Add evidence-gated paired maker strategy

This commit is contained in:
Theodore Song
2026-08-19 14:30:41 -04:00
parent c983c1e99f
commit e5efc85f65
6 changed files with 524 additions and 35 deletions
+189 -15
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 51 · build 62</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 52 · build 63</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 62 active:</b> unproven directional signals train the event-clustered learner without risking cash. Promotion now requires separate positive 24-hour and 72-hour net-of-cost evidence from independent events under the current strategy; missed grading windows expire instead of being mislabeled. Cloud provider failures remain visible after every completed cycle. Live-priced complete YES or NO negative-risk bundles may trade when their worst-case payout remains positive after estimated costs; cached bundle prices never open positions. This remains paper trading; profits are not guaranteed.</div>
<div class="live-build-banner"><b>Build 63 active:</b> Value Hunter can stage paired resting bids in low-volatility, reward-scoring markets when the two quotes cost less than their $1 combined payout. Paper profit is never credited from a hypothetical reward or unfilled order: each leg must cross on a later live cycle, and unmatched inventory is closed after 24 hours. Directional promotion still requires separate positive 24-hour and 72-hour evidence from independent current-strategy events. 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 62 · Adaptive strategy 51 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
Build 63 · Adaptive strategy 52 · 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,9 +774,9 @@ 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 = 62;
const SUGGESTION_ENGINE_VERSION = 51;
const PREVIOUS_STRATEGY_VERSION = 50;
const BUILD_VERSION = 63;
const SUGGESTION_ENGINE_VERSION = 52;
const PREVIOUS_STRATEGY_VERSION = 51;
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
function normalizedStrategyVersion(value){
const version=Number(value||0);
@@ -861,7 +861,7 @@ async function refreshTradeEmailStatus(){
Five core and five aggressive strategies share the same live suggestion pool. */
const AGENTS = [
{id:"value", name:"Value Hunter", emoji:"🎯", color:"#7c8cff", kind:"strategy",
blurb:"Scans complete negative-risk events for executable multi-leg pricing gaps whose worst-case payout remains positive after estimated costs. Directional ideas stay observation-only until their own category, side, and signal cohorts earn adaptive promotion.",
blurb:"Runs the arena's non-directional book: complete negative-risk bundles plus paired resting YES/NO bids in calm, reward-scoring markets. A paired quote earns nothing until live prices cross each leg; two fills below $1 lock settlement value, while unmatched inventory is unwound after 24 hours.",
rank:(s)=>[...s].sort((a,b)=>(Math.abs(b.net_edge||b.edge||0)*120+b.conviction+Number(b.evidence_score||0)*18)-(Math.abs(a.net_edge||a.edge||0)*120+a.conviction+Number(a.evidence_score||0)*18)),
maxNew:4, maxFrac:0.055, minConv:58, kelly:0.24, targetExposure:0.62, drawdownExposure:0.42},
{id:"momentum", name:"Momentum Chaser", emoji:"🚀", color:"#34d399", kind:"strategy",
@@ -1011,7 +1011,10 @@ function normalizeMarket(raw,{allowClosed=false}={}){
spread:toNum(raw.spread),best_bid:toNum(raw.bestBid),best_ask:toNum(raw.bestAsk),
price_change_1h:toNum(raw.oneHourPriceChange),price_change_1d:toNum(raw.oneDayPriceChange),
price_change_1w:toNum(raw.oneWeekPriceChange),price_change_1m:toNum(raw.oneMonthPriceChange),
days_to_resolution:daysUntil(raw.endDate),image:raw.image||"",
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),
closed:Boolean(raw.closed),accepting_orders:raw.acceptingOrders!==false,
url:ev.slug?`https://polymarket.com/event/${ev.slug}`:""};
}
@@ -1151,6 +1154,11 @@ const NEG_RISK_EVENT_SCAN_LIMIT=500;
const NEG_RISK_MIN_LIQUIDITY=1000;
const NEG_RISK_MIN_NET_PROFIT=0.003;
const NEG_RISK_MIN_NET_RETURN=0.0015;
const MAKER_MAX_QUOTES=6;
const MAKER_MAX_QUOTE_CAPITAL_PCT=0.02;
const MAKER_TOTAL_CAPITAL_PCT=0.10;
const MAKER_QUOTE_EXPIRY_HOURS=24;
const MAKER_MIN_LOCK_MARGIN=0.005;
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
function liquiditySignal(m){const vol=Math.min(1,Math.log10(m.volume+1)/6.0);const liq=Math.min(1,Math.log10(m.liquidity+1)/5.3);return 0.6*vol+0.4*liq;}
function momentumSignal(m){const da=m.volume_1wk?m.volume_1wk/7:0;if(da<=0)return m.volume_24hr>0?0.3:0;return clamp((m.volume_24hr/da-0.5)/2.0,0,1);}
@@ -1398,7 +1406,7 @@ function generateSuggestions(markets,total=SUGGESTION_TOTAL,perCategory=SUGGESTI
}
/* ---------- Multi-agent store ---------- */
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],closed:[],history:[],snapshots:[],stopped:{},lastDecision:null};}
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],maker_quotes:[],closed:[],history:[],snapshots:[],stopped:{},lastDecision:null};}
function defaultState(){const agents={};AGENTS.forEach(a=>agents[a.id]=defaultPortfolio());return {date:null,last_run:null,last_cycle_hour:null,
engine_version:BUILD_VERSION,strategy_version:SUGGESTION_ENGINE_VERSION,engine_started_at:nowIso(),build_started_at:nowIso(),agents,signal_ledger:defaultSignalLedger(),seeded:false};}
function reconcileStateVersions(st){
@@ -1438,6 +1446,7 @@ function loadState(){
AGENTS.forEach(a=>{
if(!st.agents[a.id])st.agents[a.id]=defaultPortfolio();
if(!st.agents[a.id].stopped)st.agents[a.id].stopped={};
if(!Array.isArray(st.agents[a.id].maker_quotes))st.agents[a.id].maker_quotes=[];
if(!("lastDecision" in st.agents[a.id]))st.agents[a.id].lastDecision=null;
});
migrated=reconcileStateVersions(st)||migrated;
@@ -1449,6 +1458,7 @@ function compactPortfolioForSync(p,limits=SYNC_LIMITS){
if(!p||typeof p!=="object")return p;
const out=Object.assign({},p);
out.positions=Array.isArray(p.positions)?p.positions:[];
out.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes.slice(-MAKER_MAX_QUOTES):[];
out.closed=Array.isArray(p.closed)?p.closed.slice(-limits.closed):[];
out.history=Array.isArray(p.history)?p.history.slice(-limits.history):[];
out.snapshots=Array.isArray(p.snapshots)?p.snapshots.slice(-limits.snapshots):[];
@@ -1519,7 +1529,9 @@ function compactCachedMarket(m){
volume_24hr:m.volume_24hr,volume_1wk:m.volume_1wk,liquidity:m.liquidity,spread:m.spread,
best_bid:m.best_bid,best_ask:m.best_ask,price_change_1h:m.price_change_1h,
price_change_1d:m.price_change_1d,price_change_1w:m.price_change_1w,price_change_1m:m.price_change_1m,
days_to_resolution:m.days_to_resolution,end_date:m.end_date,closed:m.closed,accepting_orders:m.accepting_orders};
days_to_resolution:m.days_to_resolution,end_date:m.end_date,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};
}
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),
@@ -2298,6 +2310,10 @@ function markToMarket(p,priceMap,cfg=null,{policyExits=false,executeTrades=true}
pos.peak_price=+Math.max(Number(pos.peak_price||pos.entry_price||price),price).toFixed(4);
if(executeTrades)delete pos.price_status;else pos.price_status="cached-mark-only";
if(!executeTrades){stillOpen.push(pos);continue;}
if(pos.signal_type==="maker-pair"&&pos.maker_pair_pending){
if(fresh.closed||fresh.accepting_orders===false){closePosition(p,pos,"Paired maker inventory settled before the second fill","CLOSE");continue;}
stillOpen.push(pos);continue;
}
if(pos.requires_complete_bundle){
if(fresh.closed||fresh.accepting_orders===false){closePosition(p,pos,"Complete bundle leg settled","CLOSE");continue;}
stillOpen.push(pos);continue;
@@ -2321,6 +2337,108 @@ function markToMarket(p,priceMap,cfg=null,{policyExits=false,executeTrades=true}
}
p.positions=stillOpen;
}
function makerPairCandidate(m){
if(!m||m.closed||m.accepting_orders===false)return null;
const bid=Number(m.best_bid),ask=Number(m.best_ask),spread=ask-bid,tick=Math.max(0.001,Number(m.tick_size||0.01));
const reward=Number(m.rewards_daily_rate||0),minSize=Math.max(5,Number(m.rewards_min_size||0),Number(m.order_min_size||0));
const maxSpread=Number(m.rewards_max_spread||0)/100,dayMove=Math.abs(Number(m.price_change_1d||0));
const yesQuote=bid,noQuote=1-ask,pairedCost=yesQuote+noQuote,requiredCapital=minSize*pairedCost;
if(!(reward>=1&&spread>=0.02&&spread<=0.08&&maxSpread>0&&spread/2<=maxSpread&&yesQuote>=tick&&noQuote>=tick
&&pairedCost<=1-MAKER_MIN_LOCK_MARGIN&&dayMove<=Math.max(0.02,spread)&&Number(m.liquidity||0)>=5000
&&Number(m.volume_24hr||0)>=2000&&requiredCapital>0))return null;
const rewardYield=reward/requiredCapital,competition=clamp(Number(m.competitive||0),0,1);
return {market_id:String(m.id),condition_id:m.condition_id||"",question:m.question,event:m.event,url:m.url||"",category:m.category||"Other",
yes_quote:+yesQuote.toFixed(4),no_quote:+noQuote.toFixed(4),paired_cost:+pairedCost.toFixed(4),units:+minSize.toFixed(2),
required_capital:+requiredCapital.toFixed(2),locked_profit:+(minSize*(1-pairedCost)).toFixed(2),reward_daily_rate:+reward.toFixed(3),
competitive:+competition.toFixed(4),spread:+spread.toFixed(4),tick_size:tick,day_move:+dayMove.toFixed(4),
score:+(rewardYield*(1-competition*0.5)*100+spread*25).toFixed(4)};
}
function stageMakerQuotes(p,markets){
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
const eq=equity(p),existing=new Set(p.maker_quotes.map(quote=>String(quote.market_id))),held=new Set((p.positions||[]).map(pos=>String(pos.market_id)));
let reserved=p.maker_quotes.reduce((sum,quote)=>sum+Number(quote.required_capital||0),0),staged=0;
const candidates=(markets||[]).map(makerPairCandidate).filter(Boolean).sort((a,b)=>b.score-a.score);
for(const candidate of candidates){
if(p.maker_quotes.length>=MAKER_MAX_QUOTES||reserved>=eq*MAKER_TOTAL_CAPITAL_PCT)break;
if(existing.has(candidate.market_id)||held.has(candidate.market_id)||candidate.required_capital>eq*MAKER_MAX_QUOTE_CAPITAL_PCT
||reserved+candidate.required_capital>Math.min(p.cash,eq*MAKER_TOTAL_CAPITAL_PCT))continue;
const quote=Object.assign({},candidate,{id:`maker:${candidate.market_id}:${Date.now()}:${p.maker_quotes.length}`,
created_at:cycleIso(),strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION,
yes_filled_at:null,no_filled_at:null,yes_fill_price:null,no_fill_price:null});
p.maker_quotes.push(quote);existing.add(candidate.market_id);reserved+=candidate.required_capital;staged++;
p.history.push({date:logDay(),action:"QUOTE",question:candidate.question,side:"PAIR",
detail:`Staged ${candidate.units} resting YES @ ${pct(candidate.yes_quote)} and NO @ ${pct(candidate.no_quote)} · paired cost ${pct(candidate.paired_cost)} · ${fmtUSD(candidate.locked_profit)} locked settlement margin only if both legs fill · no hypothetical reward credited`});
}
return {staged,active:p.maker_quotes.length,candidates:candidates.length,reserved:+reserved.toFixed(2)};
}
function makerQuotePosition(p,quote,side){return (p.positions||[]).find(pos=>pos.maker_quote_id===quote.id&&pos.side===side);}
function fillMakerLeg(p,quote,side,fresh){
const price=Number(side==="YES"?quote.yes_quote:quote.no_quote),cost=+(Number(quote.units)*price).toFixed(2);
if(!(cost>0)||cost>p.cash)return false;
const current=side==="YES"?Number(fresh.yes_price):Number(fresh.no_price),tokenIndex=side==="YES"?0:1;
p.cash=+(p.cash-cost).toFixed(2);
p.positions.push({market_id:quote.market_id,question:quote.question,side,shares:quote.units,token_id:fresh.clob_token_ids&&fresh.clob_token_ids[tokenIndex]||null,
entry_price:+price.toFixed(4),current_price:+current.toFixed(4),cost,value:+(quote.units*current).toFixed(2),original_shares:quote.units,original_cost:cost,
unrealized_pnl:+(quote.units*current-cost).toFixed(2),conviction:80,peer_conviction:80,category:quote.category,opened_at:cycleIso(),url:quote.url||"",
peer_note:"paired resting quote",entry_reason:"Resting maker bid crossed on a later live cycle; no reward assumed",net_edge:1-quote.paired_cost,
evidence_score:1,evidence_source_count:0,friction:0,chase_penalty:0,quality:"maker-pair",strategy_version:SUGGESTION_ENGINE_VERSION,
build_version:BUILD_VERSION,momentum_strength:0,signal_strength:1,signal_confidence:1,signal_type:"maker-pair",price_change_1d:quote.day_move,
price_change_1w:0,days_to_resolution:null,jump_risk:false,maker_quote_id:quote.id,maker_pair_pending:true,requires_complete_bundle:false,
learning_score:0,learning_confidence:0,market_learning_score:0,market_learning_confidence:0,learning_state:"maker-fill",market_learning_state:"maker-fill",
historical_prior_score:0,historical_prior_confidence:0,historical_prior_features:[],historical_requires_promotion:false,learning_multiplier:1,
learning_exploration:false,risk_budget_pct:2,peak_price:+current.toFixed(4),gain_stops:{},stop_losses:{}});
quote[`${side.toLowerCase()}_filled_at`]=cycleIso();quote[`${side.toLowerCase()}_fill_price`]=+price.toFixed(4);
p.history.push({date:logDay(),action:"MAKER_FILL",question:quote.question,side,
detail:`Resting ${side} bid filled at ${pct(price)} for ${fmtUSD(cost)}; the complementary quote remains live and this inventory is not counted as a locked pair yet`});
return true;
}
function completeMakerPair(p,quote){
const positions=[makerQuotePosition(p,quote,"YES"),makerQuotePosition(p,quote,"NO")];
if(positions.some(pos=>!pos))return false;
const margin=1-Number(quote.yes_fill_price)-Number(quote.no_fill_price);
if(margin<MAKER_MIN_LOCK_MARGIN-0.0001)return false;
positions.forEach(pos=>{pos.maker_pair_pending=false;pos.requires_complete_bundle=true;pos.bundle_id=quote.id;pos.bundle_name=quote.question;
pos.bundle_leg_count=2;pos.bundle_payout_per_unit=1;pos.bundle_net_profit_per_unit=+margin.toFixed(4);});
p.history.push({date:logDay(),action:"MAKER_LOCK",question:quote.question,side:"PAIR",
detail:`Both resting legs filled for ${pct(1-margin)} combined · locked settlement value ${fmtUSD(Number(quote.units)*margin)} before any separately verified reward`});
return true;
}
function unwindMakerInventory(p,quote,fresh){
const side=quote.yes_filled_at&&!quote.no_filled_at?"YES":quote.no_filled_at&&!quote.yes_filled_at?"NO":null;
if(!side)return true;
const pos=makerQuotePosition(p,quote,side);if(!pos)return true;
const executable=side==="YES"?Number(fresh.best_bid):1-Number(fresh.best_ask);
if(!(executable>0&&executable<1))return false;
pos.current_price=+executable.toFixed(4);pos.value=+(pos.shares*pos.current_price).toFixed(2);pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
closePosition(p,pos,`Paired maker quote expired after ${MAKER_QUOTE_EXPIRY_HOURS}h without the complementary fill`,"MAKER_EXIT");
p.positions=(p.positions||[]).filter(item=>item!==pos);
return true;
}
function manageMakerQuotes(p,marketMap,{executeTrades=false}={}){
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
if(!executeTrades)return {active:p.maker_quotes.length,fills:0,locked:0,expired:0};
const keep=[];let fills=0,locked=0,expired=0;
for(const quote of p.maker_quotes){
const fresh=marketMap[String(quote.market_id)];
if(!fresh){keep.push(quote);continue;}
const age=hoursSince(quote.created_at),oldEnough=age>=RUN_INTERVAL_MS/3600000*0.8;
if(oldEnough&&!quote.yes_filled_at&&Number(fresh.best_ask)>0&&Number(fresh.best_ask)<=Number(quote.yes_quote)+0.00001){if(fillMakerLeg(p,quote,"YES",fresh))fills++;}
const noAsk=1-Number(fresh.best_bid);
if(oldEnough&&!quote.no_filled_at&&Number(fresh.best_bid)>0&&noAsk<=Number(quote.no_quote)+0.00001){if(fillMakerLeg(p,quote,"NO",fresh))fills++;}
if(quote.yes_filled_at&&quote.no_filled_at){if(completeMakerPair(p,quote)){locked++;continue;}}
if(age>=MAKER_QUOTE_EXPIRY_HOURS){if(unwindMakerInventory(p,quote,fresh)){expired++;continue;}}
if(quote.yes_filled_at&&!quote.no_filled_at){
const cap=1-Number(quote.yes_fill_price)-MAKER_MIN_LOCK_MARGIN,currentNoBid=1-Number(fresh.best_ask);
quote.no_quote=+Math.min(cap,Math.max(Number(quote.no_quote),currentNoBid)).toFixed(4);
}else if(quote.no_filled_at&&!quote.yes_filled_at){
const cap=1-Number(quote.no_fill_price)-MAKER_MIN_LOCK_MARGIN,currentYesBid=Number(fresh.best_bid);
quote.yes_quote=+Math.min(cap,Math.max(Number(quote.yes_quote),currentYesBid)).toFixed(4);
}
keep.push(quote);
}
p.maker_quotes=keep;
return {active:keep.length,fills,locked,expired};
}
function peerMarketStats(st,selfId){
const stats={};
AGENTS.forEach(a=>{
@@ -2328,6 +2446,7 @@ function peerMarketStats(st,selfId){
const p=st.agents&&st.agents[a.id]; if(!p)return;
const ret=(equity(p)/Math.max(p.starting_balance||STARTING_BALANCE,1)-1)*100;
(p.positions||[]).forEach(pos=>{
if(pos.requires_complete_bundle)return;
if(!isMaterialPosition(p,pos))return;
const id=pos.market_id||pos.asset; if(!id)return;
const key=`${id}:${pos.side}`;
@@ -2388,6 +2507,7 @@ function reduceStrategyOverlap(st){
AGENTS.filter(a=>a.kind==="strategy").forEach(cfg=>{
const p=st.agents&&st.agents[cfg.id]; if(!p)return;
(p.positions||[]).forEach(pos=>{
if(pos.requires_complete_bundle)return;
if(!isMaterialPosition(p,pos))return;
const id=pos.market_id||pos.asset; if(!id)return;
if(!groups[id])groups[id]=[];
@@ -2726,6 +2846,7 @@ async function runDailyCycle(){
const positionIds=new Set();
AGENTS.forEach(a=>
(st.agents[a.id].positions||[]).forEach(pos=>{if(pos.market_id)positionIds.add(String(pos.market_id));}));
AGENTS.forEach(a=>(st.agents[a.id].maker_quotes||[]).forEach(quote=>{if(quote.market_id)positionIds.add(String(quote.market_id));}));
setStatus("marking positions…",true);
const priceMap={};
const cachedById={};
@@ -2758,6 +2879,9 @@ async function runDailyCycle(){
const leaderEq=preBoard[0]?preBoard[0].eq:STARTING_BALANCE;
const cachePolicy=offlineCachePolicy(cacheAgeMs);
const executeTrades=runMode==="live"||cachePolicy.exitsAllowed;
const quoteMarketMap=Object.assign({},cachedById,freshById);
const valuePortfolio=st.agents.value;
const makerActivity=manageMakerQuotes(valuePortfolio,quoteMarketMap,{executeTrades:runMode==="live"&&executeTrades});
for(const cfg of AGENTS){
const p=st.agents[cfg.id];
markToMarket(p,priceMap,cfg,{policyExits:executeTrades,executeTrades});
@@ -2773,6 +2897,14 @@ async function runDailyCycle(){
const occupied=occupiedStrategyMarkets(st,cfg.id);
claimedMarkets.forEach(id=>occupied.add(id));
openPositions(p,cfg,cfg.rank(cycleSuggestions),focus,decision,occupied,peerMarketStats(st,cfg.id)).forEach(id=>claimedMarkets.add(id));
if(cfg.id==="value"&&runMode==="live"&&entriesAllowed){
const staged=stageMakerQuotes(p,analysisMarkets);
p.lastDecision=Object.assign({},p.lastDecision,{makerQuotes:p.maker_quotes.length,makerCandidates:staged.candidates,makerStaged:staged.staged,
makerFills:makerActivity.fills,makerLocked:makerActivity.locked,makerExpired:makerActivity.expired,makerReserved:staged.reserved});
if(staged.staged||makerActivity.fills||makerActivity.locked||makerActivity.expired){
p.lastDecision.allocationStatus=`${p.lastDecision.allocationStatus||""} Paired maker book: ${p.maker_quotes.length} active quote${p.maker_quotes.length===1?"":"s"}, ${makerActivity.fills} fill${makerActivity.fills===1?"":"s"}, ${makerActivity.locked} newly locked pair${makerActivity.locked===1?"":"s"}; no hypothetical rewards credited.`.trim();
}
}
recordSnapshot(p);
}
st.date=todayStr();st.last_run=SNAP_TS;st.last_cycle_hour=hour;st.run_mode=runMode;
@@ -2961,7 +3093,8 @@ function decisionSummary(p){
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 checkpoint observations across ${d.marketLearning.events||d.marketLearning.markets||0} event clusters / ${d.marketLearning.markets||0} markets, graded separately near ${SIGNAL_EVAL_HORIZONS.join("h and ")}h (${d.marketLearning.current_samples||0} observations / ${d.marketLearning.current_events||0} events under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future checkpoint${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired checkpoints`:""}; ${d.marketLearning.promoted_buckets||0} horizon-specific feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Promotion requires positive current-strategy evidence at both horizons across independent events. Missed windows expire rather than borrowing a later price. New observations prioritize under-sampled signal/side/category cohorts and independent events before repeats. Correlated outcome markets in one event count as one effective outcome. Historical prior: every directional trend and reversal remains observation-only until its exact recent cohorts independently promote; settlement-jump barriers stay excluded.`:"";
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}`;
const maker=d.makerQuotes!=null?` Paired maker book: ${d.makerQuotes} active, ${d.makerFills||0} fills and ${d.makerLocked||0} completed locks this cycle, ${fmtUSD(d.makerReserved||0)} quote capital reserved. Rewards remain excluded until externally verified.`:"";
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}${maker}${exposure}${allocation}${candidates}${blockers}`;
}
function renderAgentBrief(cfg,p,st){
const root=$("agentBrief"); if(!root)return;
@@ -2970,11 +3103,13 @@ function renderAgentBrief(cfg,p,st){
const cadence=`${cfg.maxNew||0} new trades max per cycle`;
const exitRule=`Exit after a confirmed reversal, a 12h unconfirmed fade after 48h, a trailing-profit retrace, or ${EXIT_STALE_DAYS}d stale while losing; Politics trends get 72h before ordinary signal exits`;
const thesis=agentPlainBlurb(cfg,st);
const quoteSummary=(p.maker_quotes||[]).slice(0,3).map(quote=>`${quote.question.slice(0,34)}${quote.question.length>34?"...":""}: YES ${pct(quote.yes_quote)} + NO ${pct(quote.no_quote)}${quote.yes_filled_at||quote.no_filled_at?" (one leg filled)":""}`).join("; ");
root.innerHTML=`
<div class="brief-main" style="border-top:3px solid ${cfg.color}">
<div class="brief-title"><span>${cfg.emoji}</span><span>${cfg.name}</span></div>
<p>${esc(thesis)}</p>
<p style="margin-top:9px"><b>Adaptation:</b> ${esc(decisionSummary(p))}</p>
${quoteSummary?`<p style="margin-top:9px"><b>Resting maker quotes:</b> ${esc(quoteSummary)}</p>`:""}
</div>
<div class="brief-mini"><div class="k">Style</div><div class="v">${esc(risk)}</div></div>
<div class="brief-mini"><div class="k">Trade Rule</div><div class="v">${esc(cadence)}</div></div>
@@ -3214,6 +3349,7 @@ function renderPortfolioTab(){
{ic:"🕒",label:"24h change",value:format24h(change24h),cls:signClass(change24h.pct)},
{ic:"🧪",label:"Adaptive",value:fmtPct(engineBase>0?(eq/engineBase-1)*100:0),cls:signClass(enginePnl)},
{ic:"📂",label:"Open",value:p.positions.length},
{ic:"⇄",label:"Resting quotes",value:(p.maker_quotes||[]).length},
];
$("statsPf").innerHTML=stats.map(s=>`<div class="stat"><div class="ic">${s.ic}</div><div class="label">${s.label}</div><div class="value ${s.cls||""}">${s.value}</div></div>`).join("");
const sort=$("positionSort");
@@ -3265,7 +3401,7 @@ function renderAgentTechnical(agentId){
}
function agentCompetitionPlan(cfg,row,rank,leader){
if(rank===1)return "Plan: press the lead through the strongest qualifying trades while keeping stop-loss and exposure limits active.";
if(cfg.id==="value")return "Plan: close the gap by waiting for the cleanest confirmed movement after friction and avoiding crowded trades without enough signal margin.";
if(cfg.id==="value")return "Plan: close the gap with complete pricing bundles and paired resting bids, counting return only after both complementary legs fill below their combined payout and unwinding unmatched inventory after 24 hours.";
if(cfg.id==="momentum")return "Plan: attack fast-moving markets where fresh volume confirms attention, hoping speed beats slower value strategies.";
if(cfg.id==="favorite")return "Plan: grind upward through high-probability favorites, aiming to outlast more volatile agents during choppy markets.";
if(cfg.id==="longshot")return "Plan: keep risk small but search for one underpriced outsider that can reprice sharply and leapfrog the leaderboard.";
@@ -3279,7 +3415,7 @@ function agentCompetitionPlan(cfg,row,rank,leader){
}
function recentActionSummary(p){
const recent=(p.history||[]).slice(-8);
const counts={STOP:0,GAIN:0,EXIT:0,CLOSE:0,OPEN:0,RISK:0};
const counts={STOP:0,GAIN:0,EXIT:0,CLOSE:0,OPEN:0,RISK:0,QUOTE:0,MAKER_FILL:0,MAKER_LOCK:0,MAKER_EXIT:0};
recent.forEach(h=>{if(counts[h.action]!==undefined)counts[h.action]++;});
const bits=[];
if(counts.STOP)bits.push(`${counts.STOP} stop-loss sale${counts.STOP===1?"":"s"}`);
@@ -3287,6 +3423,10 @@ function recentActionSummary(p){
if(counts.EXIT||counts.CLOSE)bits.push(`${counts.EXIT+counts.CLOSE} exit${counts.EXIT+counts.CLOSE===1?"":"s"}`);
if(counts.RISK)bits.push(`${counts.RISK} binary-risk rebalance${counts.RISK===1?"":"s"}`);
if(counts.OPEN)bits.push(`${counts.OPEN} new entr${counts.OPEN===1?"y":"ies"}`);
if(counts.QUOTE)bits.push(`${counts.QUOTE} paired quote${counts.QUOTE===1?"":"s"} staged`);
if(counts.MAKER_FILL)bits.push(`${counts.MAKER_FILL} maker fill${counts.MAKER_FILL===1?"":"s"}`);
if(counts.MAKER_LOCK)bits.push(`${counts.MAKER_LOCK} locked pair${counts.MAKER_LOCK===1?"":"s"}`);
if(counts.MAKER_EXIT)bits.push(`${counts.MAKER_EXIT} unmatched maker exit${counts.MAKER_EXIT===1?"":"s"}`);
return bits.length?bits.join(", "):"no major recent trade actions";
}
function snapshotNearDayAgo(snaps,lastTime){
@@ -3297,7 +3437,7 @@ function snapshotNearDayAgo(snaps,lastTime){
}
function dailyActionSummary(p,lastTime){
const cutoff=lastTime-86400000;
const counts={STOP:0,GAIN:0,EXIT:0,CLOSE:0,OPEN:0,RISK:0};
const counts={STOP:0,GAIN:0,EXIT:0,CLOSE:0,OPEN:0,RISK:0,QUOTE:0,MAKER_FILL:0,MAKER_LOCK:0,MAKER_EXIT:0};
(p.history||[]).forEach(h=>{
const t=h.date?new Date(`${h.date}T12:00:00`).getTime():NaN;
if(Number.isFinite(t)&&t>=cutoff&&counts[h.action]!==undefined)counts[h.action]++;
@@ -3308,6 +3448,10 @@ function dailyActionSummary(p,lastTime){
if(counts.STOP)bits.push(`${counts.STOP} stop-loss sale${counts.STOP===1?"":"s"}`);
if(counts.EXIT||counts.CLOSE)bits.push(`${counts.EXIT+counts.CLOSE} exit${counts.EXIT+counts.CLOSE===1?"":"s"}`);
if(counts.RISK)bits.push(`${counts.RISK} binary-risk rebalance${counts.RISK===1?"":"s"}`);
if(counts.QUOTE)bits.push(`${counts.QUOTE} paired quote${counts.QUOTE===1?"":"s"} staged`);
if(counts.MAKER_FILL)bits.push(`${counts.MAKER_FILL} maker fill${counts.MAKER_FILL===1?"":"s"}`);
if(counts.MAKER_LOCK)bits.push(`${counts.MAKER_LOCK} locked pair${counts.MAKER_LOCK===1?"":"s"}`);
if(counts.MAKER_EXIT)bits.push(`${counts.MAKER_EXIT} unmatched maker exit${counts.MAKER_EXIT===1?"":"s"}`);
return bits.length?bits.join(", "):recentActionSummary(p);
}
function recentClosedAttribution(p,lastTime){
@@ -4560,7 +4704,8 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
onePendingObservationPerMarketSide:true,oldestPendingEvidenceFirst:true,coverageAwareObservationSampling:true,uncertaintyGatedCalibration:true,
directionalSignalsRequirePromotion:true,liveOnlyCompleteBundles:true,negativeRiskBundleSides:["YES","NO"],negativeRiskEventScanLimit:NEG_RISK_EVENT_SCAN_LIMIT,
negativeRiskMinimumNetReturnPct:NEG_RISK_MIN_NET_RETURN*100,
historicalPrior:"All directional trends and reversals require positive independent cohort promotion; Sports and Crypto trends remain excluded; exact ranges and path-dependent barriers are excluded; live-priced complete negative-risk bundles may trade when positive after estimated costs"}),
pairedMakerQuotes:true,makerQuoteExpiryHours:MAKER_QUOTE_EXPIRY_HOURS,makerMinimumLockMarginPct:MAKER_MIN_LOCK_MARGIN*100,hypotheticalRewardsCredited:false,
historicalPrior:"All directional trends and reversals require positive independent cohort promotion; Sports and Crypto trends remain excluded; exact ranges and path-dependent barriers are excluded; live-priced complete negative-risk bundles and crossed paired maker quotes may trade"}),
});
function runEngineSelfTest(){
const market=(overrides={})=>Object.assign({
@@ -4727,6 +4872,23 @@ function runEngineSelfTest(){
retiredFixture.pos.strategy_version=PREVIOUS_STRATEGY_VERSION;
markToMarket(retiredFixture.book,{[retiredFixture.pos.market_id]:market({id:retiredFixture.pos.market_id,yes_price:0.45,no_price:0.55})},AGENTS[0],{policyExits:true,executeTrades:true});
const nonBinaryMarketRejected=normalizeMarket({id:"over-under-test",question:"Over/Under 2.5",outcomes:'["Over","Under"]',outcomePrices:'["0.5","0.5"]',acceptingOrders:true})===null;
const makerMarket=market({id:"maker-test",question:"Will the paired quote test pass?",yes_price:0.42,no_price:0.58,best_bid:0.40,best_ask:0.44,spread:0.04,
tick_size:0.01,order_min_size:5,rewards_daily_rate:10,rewards_min_size:50,rewards_max_spread:4.5,competitive:0.8,price_change_1d:0.01});
const makerBook=defaultPortfolio(),makerStage=stageMakerQuotes(makerBook,[makerMarket]),makerQuote=makerBook.maker_quotes[0];
makerQuote.created_at=hoursAgo(1);
const makerFirst=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
const makerSingleFill=makerBook.positions.length===1&&makerBook.positions[0].side==="YES"&&makerBook.maker_quotes.length===1;
const makerSecond=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.60,no_price:0.40,best_bid:0.57,best_ask:0.61}))},{executeTrades:true});
const makerExpectedProfit=+(makerQuote.units*(1-Number(makerQuote.yes_fill_price)-Number(makerQuote.no_fill_price))).toFixed(2);
const makerOverlapState=defaultState();makerOverlapState.agents.value=makerBook;reduceStrategyOverlap(makerOverlapState);
const makerPairSurvivesOverlap=makerBook.positions.length===2&&makerBook.positions.every(pos=>pos.requires_complete_bundle);
markToMarket(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:1,no_price:0,best_bid:1,best_ask:1,closed:true,accepting_orders:false}))},AGENTS[0],{policyExits:true,executeTrades:true});
const makerExpiryBook=defaultPortfolio();stageMakerQuotes(makerExpiryBook,[makerMarket]);makerExpiryBook.maker_quotes[0].created_at=hoursAgo(1);
manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
makerExpiryBook.maker_quotes[0].created_at=hoursAgo(25);
const makerExpired=manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.36,no_price:0.64,best_bid:0.35,best_ask:0.37}))},{executeTrades:true});
const makerOfflineBook=defaultPortfolio();stageMakerQuotes(makerOfflineBook,[makerMarket]);makerOfflineBook.maker_quotes[0].created_at=hoursAgo(1);
const makerOffline=manageMakerQuotes(makerOfflineBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:false});
const bundleSuggestion=negativeRiskBundleSuggestion({id:"bundle-test",title:"Three-way result",slug:"bundle-test",negRisk:true,enableNegRisk:true,tags:[{slug:"sports",label:"Sports"}],markets:[
{id:"bundle-a",question:"A wins",outcomes:'["Yes","No"]',outcomePrices:'["0.415","0.585"]',clobTokenIds:'["a-yes","a-no"]',bestBid:0.41,bestAsk:0.42,liquidityNum:30000,volumeNum:100000,volume24hr:10000,acceptingOrders:true},
{id:"bundle-b",question:"B wins",outcomes:'["Yes","No"]',outcomePrices:'["0.315","0.685"]',clobTokenIds:'["b-yes","b-no"]',bestBid:0.31,bestAsk:0.32,liquidityNum:28000,volumeNum:100000,volume24hr:10000,acceptingOrders:true},
@@ -4908,6 +5070,18 @@ function runEngineSelfTest(){
riskTrimConservesEquity:+equity(riskBook).toFixed(2)===10000,
adaptiveReturnSetsLeader:adaptiveRankFixture[0].c.id==="adaptive-leader",
},
makerLiquidity:{
stagesEligiblePairedQuote:makerStage.staged===1&&makerStage.active===1&&makerQuote.paired_cost===0.96,
firstCrossCreatesOnlyOneInventoryLeg:makerFirst.fills===1&&makerSingleFill,
secondCrossLocksComplementaryPair:makerSecond.fills===1&&makerSecond.locked===1&&makerBook.maker_quotes.length===0,
overlapCleanupPreservesLockedPair:makerPairSurvivesOverlap,
lockedPairSettlesAtQuotedProfit:makerBook.positions.length===0&&Math.abs(makerBook.cash-(10000+makerExpectedProfit))<=0.02,
unmatchedInventoryExpiresAtExecutableBid:makerExpired.expired===1&&makerExpiryBook.positions.length===0&&makerExpiryBook.maker_quotes.length===0&&Math.abs(makerExpiryBook.cash-9997.5)<=0.02,
offlineSnapshotCannotInventFills:makerOffline.fills===0&&makerOfflineBook.positions.length===0&&makerOfflineBook.maker_quotes.length===1,
noHypotheticalRewardCredited:makerStage.staged===1&&Math.abs(makerBook.cash-(10000+makerExpectedProfit))<=0.02,
rejectsUnrewardedMarket:makerPairCandidate(Object.assign({},makerMarket,{rewards_daily_rate:0}))===null,
rejectsVolatileMarket:makerPairCandidate(Object.assign({},makerMarket,{price_change_1d:0.12}))===null,
},
bundleArbitrage:{
rejectsNonBinaryMarketLabels:nonBinaryMarketRejected,
rejectsNonBinaryBundleLabels:nonBinaryBundleRejected,