Learn from verified maker quote outcomes

This commit is contained in:
Theodore Song
2026-08-19 14:49:52 -04:00
parent e5efc85f65
commit bd326a2123
3 changed files with 218 additions and 59 deletions
+32 -25
View File
@@ -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 63 also installs an offline app shell and caches timestamped
Vercel Blob. Build 64 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.
@@ -26,7 +26,7 @@ team names, Over/Under, or another pair are rejected instead of being silently
reinterpreted as Yes/No. The same semantic check applies to complete event
bundles and the offline evaluators.
Build 63 ranks the competition by each agent's return since Strategy 52 began.
Build 64 ranks the competition by each agent's return since Strategy 53 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.
@@ -37,7 +37,7 @@ The learner shrinks small samples toward neutral, caps sizing changes to
15% of candidates for deterministic exploration so a stale regime cannot become
permanent.
Strategy 52 treats each binary stake as capable of falling to zero even when the
Strategy 53 treats each binary stake as capable of falling to zero even when the
18% stop cannot fill. New core positions are capped at 2.5%-4% of equity and
aggressive positions at 3%-5%, with lower limits for near-term, extreme-price,
reversal, and fast-moving setups. Oversized positions inherited from older
@@ -74,17 +74,24 @@ Run `npm run evaluate:adaptive` for a stricter chronological search across 1,920
predefined price-action rules. It uses a 60/20/20 train, validation, and holdout
split and never selects a rule from the holdout period. The August 19 run loaded
all 300 requested histories and found no directional rule that passed both train
and validation at either 24 or 72 hours. Strategy 52 therefore keeps directional
and validation at either 24 or 72 hours. Strategy 53 therefore keeps directional
signals in the walk-forward observation ledger until current, independent-event
evidence proves an edge.
Run `npm run evaluate:liquidity` to inspect live reward-scoring markets for
paired resting YES and NO bids whose combined cost is below the $1 settlement
payout. Strategy 52 lets Value Hunter stage a maximum of six paper quote pairs,
reserving at most 10% of equity and 2% per quote. A later live cycle must cross
each bid before a fill is recorded. The first unmatched leg is unwound after 24
hours; once both legs fill, the complete hedge is held intact through settlement.
The engine does not credit hypothetical fills or unverified liquidity rewards.
payout. Strategy 53 lets Value Hunter stage a maximum of six paper quote pairs,
reserving at most 10% of equity and 2% per quote. A later live cycle must show
that each bid was touched in Polymarket's public CLOB price history or crossed by
the current executable book before a paper fill is recorded. Repriced legs
ignore all history from before the new
quote timestamp. The first unmatched leg is unwound after 24 hours; once both
legs fill, the complete hedge is held intact through settlement. Locked pairs,
adverse one-leg exits, and unfilled attempts feed a persistent category and
spread-cohort learner. At least five outcomes are required before profitable
cohorts can scale up to 1.5x or losing cohorts rotate out, with a deterministic
20% exploration lane preserving adaptation. The engine does not credit
hypothetical fills or unverified liquidity rewards.
See Polymarket's official [fees](https://docs.polymarket.com/trading/fees),
[maker rebates](https://docs.polymarket.com/market-makers/maker-rebates), and
[liquidity rewards](https://docs.polymarket.com/market-makers/liquidity-rewards)
@@ -96,7 +103,7 @@ negative in all three chronological segments. Reversals averaged -3.69%, with a
market-clustered 90% interval entirely below zero. Crypto and Sports were also
negative but covered only three and five markets. The 24-hour cohort improved to
-0.82% row mean and +1.31% market mean, with no rule robustly negative across all
segments. Strategy 52 therefore keeps reversal entries observation-only until
segments. Strategy 53 therefore keeps reversal entries observation-only until
their recent signal and quality cohorts independently earn promotion, retains
their signals for paper grading, and evaluates adaptation at 24 and 72 hours.
@@ -105,7 +112,7 @@ markets with no failures and produced 3,597 net-of-cost 24-hour outcomes across
142 markets. No tested follow or fade rule was robustly positive. Crypto trends
averaged -3.83% per observation and -3.99% per market; Sports trends averaged
-5.44% and -6.61%. Both stayed negative in every chronological segment and their
market-clustered 90% intervals were entirely below zero. Strategy 52 therefore
market-clustered 90% intervals were entirely below zero. Strategy 53 therefore
keeps Crypto and Sports trends observation-only while continuing to grade them.
The August 18 event-clustered rerun loaded 499 of 500 active markets and produced
@@ -114,7 +121,7 @@ The broad mean was -1.14%, the event mean was -1.11%, and the event-clustered
90% interval stayed below zero. No tested category, side, price band, signal
strength, or combined feature cohort was robustly positive. Broad trends,
YES trends, favorite trends, strong trends, and hour-confirmed trends were all
robustly negative. Strategy 52 therefore makes every directional trend or
robustly negative. Strategy 53 therefore makes every directional trend or
reversal observation-only until its own signal, side, and category cohorts each
earn positive promotion from recent independent events. This is a strategy reset,
so current adaptive returns begin from the portfolio equity at migration.
@@ -123,7 +130,7 @@ The final August 19 300-market rerun loaded all 300 eligible Yes/No price
histories without a failure and produced 2,598 twelve-hour observations across
64 independent events. The broad row mean was -0.65% and the event-cluster mean
was -1.71%. The 72-hour event-cluster mean was -3.59% with its 90% interval below
zero, and no tested directional rule was robustly positive. Strategy 52
zero, and no tested directional rule was robustly positive. Strategy 53
therefore requires positive evidence at both 24 and 72 hours rather than
allowing one favorable short-horizon mark to authorize cash exposure.
@@ -133,24 +140,24 @@ segment and averaged -4.13%. Sports trends were negative in train and test and
averaged -3.53% at 72 hours. Politics trends were the sole cohort with positive
row-level returns in all three 72-hour segments, but its market-cluster interval
still crossed zero; that supports a longer hold test, not a larger entry bet.
Strategy 52 gives previously opened Politics trend positions that 72-hour observation window before
Strategy 53 gives previously opened Politics trend positions that 72-hour observation window before
ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget
reductions remain immediate.
Strategy 52 also subtracts a half-cent round-trip cost when grading each live
Strategy 53 also subtracts a half-cent round-trip cost 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.
Strategy 52 adds uncertainty-aware, multi-horizon promotion and demotion. A
Strategy 53 adds uncertainty-aware, multi-horizon promotion and demotion. A
matching setup must accumulate at least eight effective independent-event
observations, including at least five from the current strategy, and agree across
at least two feature views at both the 24-hour and 72-hour checkpoints before it
can risk cash. Mixed or one-horizon evidence stays observation-only instead of
being mistaken for an edge.
Build 63 enforces the documented offline boundary end to end. Cached snapshots
Build 64 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
@@ -163,26 +170,26 @@ 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 63 independently refreshes markets for due pending signals that have
Build 64 independently refreshes markets for due 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.
Build 63 also allocates the 300 pending observation slots by evidence coverage.
Build 64 also allocates the 300 pending observation slots by evidence coverage.
Under-sampled signal/side/category cohorts are observed first, followed by
under-sampled independent events and market sides, with conviction used only as
a later tie-breaker. This prevents the same popular contracts from monopolizing
the ledger and gives the learner a realistic path to promote or reject more
diverse cohorts.
Build 63 retains safe shared-state provider diagnostics from both reads and
Build 64 retains safe shared-state provider diagnostics from both reads and
writes. A device now says `local only` when Neon is paused or a Blob credential
is rejected, instead of presenting a local browser save as a successful
cross-device sync. Completed cycle statuses retain that `local only` warning
until a cloud provider succeeds. Database URLs still fail over across configured Neon
aliases without exposing credentials in the API response.
Strategy 52 coordinates high-risk exploration globally. Near-term, extreme-price,
Strategy 53 coordinates high-risk exploration globally. Near-term, extreme-price,
and other gap-prone positions may be held materially by only one agent, while
ordinary independently confirmed markets retain the two-agent cap. The robustly
negative Sports- and Crypto-trend cohorts cannot enter through exploration.
@@ -208,7 +215,7 @@ removed the apparent edge. The August 18 rerun found 35 eligible events and one
three-leg NO bundle with a 0.25%
modeled margin after estimated costs. The final August 19 scan found 49 eligible
events and no currently actionable bundle; its closest complete bundle remained
0.25% negative after modeled costs. Strategy 52 can paper-trade either a
0.25% negative after modeled costs. Strategy 53 can paper-trade either a
complete YES or complete NO bundle only from live executable prices, opens every
leg together, and holds
the hedge intact until settlement. It also requires at least a 0.15% modeled net
@@ -231,19 +238,19 @@ static settlement-direction boost from this audit.
The earlier 200-resolved-market audit found short-dated NO entries strongly
negative, but the 500-market rerun did not reproduce that loss in its newer test
segment. Strategy 52 therefore treats the result as a provisional prior instead
segment. Strategy 53 therefore treats the result as a provisional prior instead
of a permanent ban: NO entries with 21 days or less remain observation-only until
the recent walk-forward calibration promotes their matching side and duration
cohorts. Exact numeric-range contracts are excluded from new entries because a
settlement jump can pass directly through an 18% stop; the live audit found that
this failure mode caused the largest latest-day loss.
Strategy 52 also excludes path-dependent barriers such as "reach $66,000," "hit
Strategy 53 also excludes path-dependent barriers such as "reach $66,000," "hit
$90," and "dip to $62,000." These contracts can resolve abruptly as soon as the
barrier is touched, so a later hourly stop cannot reliably cap the loss. Fixed-date
level questions such as "above $66,000 on August 23" remain eligible.
Strategy 52 clusters live walk-forward observations by Polymarket event and checkpoint before
Strategy 53 clusters live walk-forward observations by Polymarket event and checkpoint before
calculating confidence. Multiple six-hour snapshots and correlated outcome
markets from the same event are averaged into one effective outcome, so one
election or tournament cannot promote or demote an entire feature cohort.
+185 -33
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 52 · build 63</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 53 · build 64</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 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>
<div class="live-build-banner"><b>Build 64 active:</b> Value Hunter verifies resting-bid touches from Polymarket's live CLOB price history, then learns from every locked pair, adverse one-leg exit, and unfilled quote. Profitable cohorts can receive modestly larger quotes; persistently losing cohorts are rotated out while a bounded exploration slot keeps the learner adaptable. Offline snapshots can value positions but cannot invent fills. 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 63 · Adaptive strategy 52 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
Build 64 · Adaptive strategy 53 · 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,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 = 63;
const SUGGESTION_ENGINE_VERSION = 52;
const PREVIOUS_STRATEGY_VERSION = 51;
const BUILD_VERSION = 64;
const SUGGESTION_ENGINE_VERSION = 53;
const PREVIOUS_STRATEGY_VERSION = 52;
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
function normalizedStrategyVersion(value){
const version=Number(value||0);
@@ -1159,6 +1159,9 @@ 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 MAKER_OUTCOME_LIMIT=120;
const MAKER_MIN_COHORT_ATTEMPTS=5;
const MAKER_TOUCH_FIDELITY_MINUTES=1;
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);}
@@ -1406,7 +1409,7 @@ function generateSuggestions(markets,total=SUGGESTION_TOTAL,perCategory=SUGGESTI
}
/* ---------- Multi-agent store ---------- */
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],maker_quotes:[],closed:[],history:[],snapshots:[],stopped:{},lastDecision:null};}
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],maker_quotes:[],maker_outcomes:[],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){
@@ -1447,6 +1450,7 @@ function loadState(){
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(!Array.isArray(st.agents[a.id].maker_outcomes))st.agents[a.id].maker_outcomes=[];
if(!("lastDecision" in st.agents[a.id]))st.agents[a.id].lastDecision=null;
});
migrated=reconcileStateVersions(st)||migrated;
@@ -1459,6 +1463,7 @@ function compactPortfolioForSync(p,limits=SYNC_LIMITS){
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.maker_outcomes=Array.isArray(p.maker_outcomes)?p.maker_outcomes.slice(-MAKER_OUTCOME_LIMIT):[];
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):[];
@@ -2337,6 +2342,55 @@ function markToMarket(p,priceMap,cfg=null,{policyExits=false,executeTrades=true}
}
p.positions=stillOpen;
}
function makerSpreadBand(spread){return Number(spread)<=0.03?"tight":Number(spread)<=0.05?"medium":"wide";}
function summarizeMakerOutcomes(rows){
const weighted=[];
(rows||[]).forEach(row=>{
const version=normalizedStrategyVersion(row.strategy_version),weight=version===SUGGESTION_ENGINE_VERSION?1:(version===PREVIOUS_STRATEGY_VERSION?0.5:0.2);
const reserved=Math.max(0.01,Number(row.reserved_capital||row.deployed_capital||0.01));
weighted.push({value:Number(row.pnl||0)/reserved,weight,status:row.status});
});
const totalWeight=weighted.reduce((sum,row)=>sum+row.weight,0),attempts=weighted.length;
const mean=totalWeight?weighted.reduce((sum,row)=>sum+row.value*row.weight,0)/totalWeight:0;
const variance=totalWeight?weighted.reduce((sum,row)=>sum+row.weight*(row.value-mean)**2,0)/totalWeight:0;
const margin=attempts>1?1.282*Math.sqrt(variance/Math.max(1,totalWeight)):1;
return {attempts,effective_attempts:+totalWeight.toFixed(2),locked:weighted.filter(row=>row.status==="locked").length,
adverse:weighted.filter(row=>row.status==="single-exit").length,unfilled:weighted.filter(row=>row.status==="unfilled").length,
pnl:+(rows||[]).reduce((sum,row)=>sum+Number(row.pnl||0),0).toFixed(2),mean:+mean.toFixed(5),
lower:+(mean-margin).toFixed(5),upper:+(mean+margin).toFixed(5),confidence:+(totalWeight/(totalWeight+8)).toFixed(4)};
}
function buildMakerProfile(p){
const outcomes=(p&&p.maker_outcomes||[]).slice(-MAKER_OUTCOME_LIMIT),buckets={};
const add=(key,row)=>{if(!buckets[key])buckets[key]=[];buckets[key].push(row);};
outcomes.forEach(row=>{add(`category:${row.category||"Other"}`,row);add(`spread:${row.spread_band||makerSpreadBand(row.spread)}`,row);});
return {version:SUGGESTION_ENGINE_VERSION,outcomes:outcomes.length,global:summarizeMakerOutcomes(outcomes),
buckets:Object.fromEntries(Object.entries(buckets).map(([key,rows])=>[key,summarizeMakerOutcomes(rows)]))};
}
function makerCandidateLearning(candidate,profile){
const matching=[profile.global,profile.buckets[`category:${candidate.category}`],profile.buckets[`spread:${makerSpreadBand(candidate.spread)}`]].filter(Boolean);
const mature=matching.filter(stats=>stats.attempts>=MAKER_MIN_COHORT_ATTEMPTS),losing=mature.find(stats=>stats.upper<0),
profitable=mature.filter(stats=>stats.lower>0&&stats.locked>=2).sort((a,b)=>b.lower-a.lower)[0];
const exploration=Boolean(losing)&&stableHash(`maker-explore:${candidate.market_id}`)%100<20;
if(losing&&!exploration)return null;
const multiplier=profitable?clamp(1+profitable.lower*8*profitable.confidence,1,1.5):1;
const units=+(candidate.units*multiplier).toFixed(2),requiredCapital=+(units*candidate.paired_cost).toFixed(2),lockedProfit=+(units*(1-candidate.paired_cost)).toFixed(2);
const learned=mature.length?mature.reduce((sum,stats)=>sum+stats.mean,0)/mature.length:0;
return Object.assign({},candidate,{units,required_capital:requiredCapital,locked_profit:lockedProfit,
maker_learning_multiplier:+multiplier.toFixed(3),maker_learning_state:profitable?"promoted":losing?"exploration":"observing",
maker_expected_return:+learned.toFixed(5),score:+(candidate.score+learned*60+(profitable?profitable.lower*100:0)).toFixed(4)});
}
function recordMakerOutcome(p,quote,status,pnl,deployedCapital){
p.maker_outcomes=Array.isArray(p.maker_outcomes)?p.maker_outcomes:[];
if(p.maker_outcomes.some(row=>row.quote_id===quote.id))return false;
const reserved=Math.max(0.01,Number(quote.required_capital||deployedCapital||0.01));
p.maker_outcomes.push({quote_id:quote.id,market_id:quote.market_id,condition_id:quote.condition_id||"",question:quote.question,
category:quote.category||"Other",spread:Number(quote.spread||0),spread_band:makerSpreadBand(quote.spread),status,pnl:+Number(pnl||0).toFixed(2),
deployed_capital:+Number(deployedCapital||0).toFixed(2),reserved_capital:+reserved.toFixed(2),return_on_reserved:+(Number(pnl||0)/reserved).toFixed(5),
created_at:quote.created_at,completed_at:cycleIso(),hours_to_outcome:+hoursSince(quote.created_at).toFixed(2),
strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION});
p.maker_outcomes=p.maker_outcomes.slice(-MAKER_OUTCOME_LIMIT);
return true;
}
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));
@@ -2346,33 +2400,61 @@ function makerPairCandidate(m){
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);
const competition=clamp(Number(m.competitive||0),0,1),activity=Math.log10(Math.max(1,Number(m.volume_24hr||0)))+Math.log10(Math.max(1,Number(m.liquidity||0)));
return {market_id:String(m.id),condition_id:m.condition_id||"",question:m.question,event:m.event,url:m.url||"",category:m.category||"Other",
clob_token_ids:(m.clob_token_ids||[]).slice(0,2),
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)};
score:+(activity*0.25+(1-competition)*1.5+spread*20-dayMove*12+Math.log10(reward+1)*0.2).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);
const makerProfile=buildMakerProfile(p);
const candidates=(markets||[]).map(makerPairCandidate).filter(Boolean).map(candidate=>makerCandidateLearning(candidate,makerProfile)).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,
const createdAt=cycleIso(),quote=Object.assign({},candidate,{id:`maker:${candidate.market_id}:${Date.now()}:${p.maker_quotes.length}`,
created_at:createdAt,yes_quote_updated_at:createdAt,no_quote_updated_at:createdAt,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`});
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 · maker learning ${candidate.maker_learning_state} at ${candidate.maker_learning_multiplier.toFixed(2)}x · no hypothetical reward credited`});
}
return {staged,active:p.maker_quotes.length,candidates:candidates.length,reserved:+reserved.toFixed(2)};
return {staged,active:p.maker_quotes.length,candidates:candidates.length,reserved:+reserved.toFixed(2),profile:makerProfile};
}
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){
async function fetchMakerTouchHistory(quotes,marketMap){
const active=(quotes||[]).filter(quote=>hoursSince(quote.created_at)<=MAKER_QUOTE_EXPIRY_HOURS+1);
if(!active.length)return {};
const tokens=new Set(),created=[];
active.forEach(quote=>{
const fresh=marketMap&&marketMap[String(quote.market_id)],ids=(quote.clob_token_ids&&quote.clob_token_ids.length===2)?quote.clob_token_ids:(fresh&&fresh.clob_token_ids||[]);
if(ids.length===2){quote.clob_token_ids=ids.slice(0,2).map(String);ids.slice(0,2).forEach(id=>tokens.add(String(id)));}
const ts=new Date(quote.created_at).getTime();if(Number.isFinite(ts))created.push(Math.floor(ts/1000));
});
if(!tokens.size||!created.length)return {};
try{
const endTs=Math.floor(Date.now()/1000),startTs=Math.max(endTs-(MAKER_QUOTE_EXPIRY_HOURS+1)*3600,Math.min(...created)-60);
const response=await fetchWithTimeout(`${CLOB}/batch-prices-history`,{method:"POST",headers:{"content-type":"application/json"},
body:JSON.stringify({markets:[...tokens].slice(0,20),start_ts:startTs,end_ts:endTs,fidelity:MAKER_TOUCH_FIDELITY_MINUTES})},PRICE_REQUEST_TIMEOUT_MS*2);
if(!response.ok)return {};
const payload=await response.json();return payload&&payload.history&&typeof payload.history==="object"?payload.history:{};
}catch(e){return {};}
}
function makerTouchEvidence(quote,side,touchHistory){
const token=quote.clob_token_ids&&quote.clob_token_ids[side==="YES"?0:1];if(!token)return null;
const updatedAt=quote[`${side.toLowerCase()}_quote_updated_at`]||quote.created_at;
const threshold=Number(side==="YES"?quote.yes_quote:quote.no_quote),created=Math.max(new Date(quote.created_at).getTime(),new Date(updatedAt).getTime())/1000+RUN_INTERVAL_MS/1000*0.8;
const points=(touchHistory&&touchHistory[String(token)]||[]).filter(point=>Number(point.t)>=created&&Number.isFinite(Number(point.p)));
const touched=points.filter(point=>Number(point.p)<=threshold+0.00001).sort((a,b)=>Number(a.t)-Number(b.t))[0];
return touched?{source:"clob-price-history",timestamp:new Date(Number(touched.t)*1000).toISOString(),price:+Number(touched.p).toFixed(4)}:null;
}
function fillMakerLeg(p,quote,side,fresh,evidence="live-book-cross"){
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;
@@ -2380,14 +2462,14 @@ function fillMakerLeg(p,quote,side,fresh){
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,
peer_note:"paired resting quote",entry_reason:`Resting maker bid touch verified by ${evidence}; 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);
quote[`${side.toLowerCase()}_filled_at`]=cycleIso();quote[`${side.toLowerCase()}_fill_price`]=+price.toFixed(4);quote[`${side.toLowerCase()}_fill_evidence`]=evidence;
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;
@@ -2399,45 +2481,60 @@ function completeMakerPair(p,quote){
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);});
const deployed=Number(quote.units)*(Number(quote.yes_fill_price)+Number(quote.no_fill_price)),pnl=Number(quote.units)*margin;
recordMakerOutcome(p,quote,"locked",pnl,deployed);
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;
if(!side){
recordMakerOutcome(p,quote,"unfilled",0,0);
p.history.push({date:logDay(),action:"MAKER_EXPIRE",question:quote.question,side:"PAIR",detail:`Both resting bids expired after ${MAKER_QUOTE_EXPIRY_HOURS}h without a verified touch; no fill or profit was recorded`});
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);
recordMakerOutcome(p,quote,"single-exit",pos.value-pos.cost,pos.cost);
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}={}){
function manageMakerQuotes(p,marketMap,{executeTrades=false,touchHistory={}}={}){
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;
const keep=[];let fills=0,locked=0,expired=0,historyTouches=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 yesTouch=oldEnough&&!quote.yes_filled_at?makerTouchEvidence(quote,"YES",touchHistory):null;
if(oldEnough&&!quote.yes_filled_at&&(yesTouch||(Number(fresh.best_ask)>0&&Number(fresh.best_ask)<=Number(quote.yes_quote)+0.00001))){
if(fillMakerLeg(p,quote,"YES",fresh,yesTouch?yesTouch.source:"live-book-cross")){fills++;if(yesTouch)historyTouches++;}
}
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++;}
const noTouch=oldEnough&&!quote.no_filled_at?makerTouchEvidence(quote,"NO",touchHistory):null;
if(oldEnough&&!quote.no_filled_at&&(noTouch||(Number(fresh.best_bid)>0&&noAsk<=Number(quote.no_quote)+0.00001))){
if(fillMakerLeg(p,quote,"NO",fresh,noTouch?noTouch.source:"live-book-cross")){fills++;if(noTouch)historyTouches++;}
}
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);
const nextNo=+Math.min(cap,Math.max(Number(quote.no_quote),currentNoBid)).toFixed(4);
if(nextNo>Number(quote.no_quote)+0.00001){quote.no_quote=nextNo;quote.no_quote_updated_at=cycleIso();}
}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);
const nextYes=+Math.min(cap,Math.max(Number(quote.yes_quote),currentYesBid)).toFixed(4);
if(nextYes>Number(quote.yes_quote)+0.00001){quote.yes_quote=nextYes;quote.yes_quote_updated_at=cycleIso();}
}
keep.push(quote);
}
p.maker_quotes=keep;
return {active:keep.length,fills,locked,expired};
return {active:keep.length,fills,locked,expired,historyTouches,profile:buildMakerProfile(p)};
}
function peerMarketStats(st,selfId){
const stats={};
@@ -2881,7 +2978,9 @@ async function runDailyCycle(){
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});
if(runMode==="live"&&(valuePortfolio.maker_quotes||[]).length)setStatus("verifying resting quote touches…",true);
const makerTouchHistory=runMode==="live"?await fetchMakerTouchHistory(valuePortfolio.maker_quotes,quoteMarketMap):{};
const makerActivity=manageMakerQuotes(valuePortfolio,quoteMarketMap,{executeTrades:runMode==="live"&&executeTrades,touchHistory:makerTouchHistory});
for(const cfg of AGENTS){
const p=st.agents[cfg.id];
markToMarket(p,priceMap,cfg,{policyExits:executeTrades,executeTrades});
@@ -2900,7 +2999,8 @@ async function runDailyCycle(){
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});
makerFills:makerActivity.fills,makerLocked:makerActivity.locked,makerExpired:makerActivity.expired,makerHistoryTouches:makerActivity.historyTouches,
makerReserved:staged.reserved,makerProfile:makerActivity.profile});
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();
}
@@ -3093,7 +3193,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.`:"";
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.`:"";
const makerStats=d.makerProfile&&d.makerProfile.global;
const maker=d.makerQuotes!=null?` Paired maker book: ${d.makerQuotes} active, ${d.makerFills||0} fills (${d.makerHistoryTouches||0} verified from CLOB history) and ${d.makerLocked||0} completed locks this cycle, ${fmtUSD(d.makerReserved||0)} quote capital reserved.${makerStats?` Learning ledger: ${makerStats.attempts} attempts, ${makerStats.locked} locked, ${makerStats.adverse} adverse exits, ${makerStats.unfilled} unfilled, ${fmtUSD(makerStats.pnl)} net.`:""} 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){
@@ -3339,6 +3440,7 @@ function renderPortfolioTab(){
document.querySelectorAll("#agentSel .segbtn").forEach(el=>el.addEventListener("click",()=>{localStorage.setItem(VIEW_KEY,el.dataset.agent);renderPortfolioTab();}));
const p=st.agents[viewId]||defaultPortfolio();const eq=equity(p),pnl=eq-p.starting_balance;
const change24h=portfolioChange24h(p,eq);
const makerProfile=buildMakerProfile(p);
const engineBase=Number(p.engine_baseline&&normalizedStrategyVersion(p.engine_baseline.version)===SUGGESTION_ENGINE_VERSION?p.engine_baseline.equity:eq),enginePnl=eq-engineBase;
renderAgentBrief(cfg,p,st);
const stats=[
@@ -3350,6 +3452,7 @@ function renderPortfolioTab(){
{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},
{ic:"Σ",label:"Maker net",value:fmtUSD(makerProfile.global.pnl),cls:signClass(makerProfile.global.pnl)},
];
$("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");
@@ -3415,7 +3518,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,QUOTE:0,MAKER_FILL:0,MAKER_LOCK:0,MAKER_EXIT: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,MAKER_EXPIRE: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"}`);
@@ -3427,6 +3530,7 @@ function recentActionSummary(p){
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"}`);
if(counts.MAKER_EXPIRE)bits.push(`${counts.MAKER_EXPIRE} unfilled quote expiry`);
return bits.length?bits.join(", "):"no major recent trade actions";
}
function snapshotNearDayAgo(snaps,lastTime){
@@ -3437,7 +3541,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,QUOTE:0,MAKER_FILL:0,MAKER_LOCK:0,MAKER_EXIT: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,MAKER_EXPIRE: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]++;
@@ -3452,6 +3556,7 @@ function dailyActionSummary(p,lastTime){
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"}`);
if(counts.MAKER_EXPIRE)bits.push(`${counts.MAKER_EXPIRE} unfilled quote expiry`);
return bits.length?bits.join(", "):recentActionSummary(p);
}
function recentClosedAttribution(p,lastTime){
@@ -3807,14 +3912,18 @@ function renderPositions(positions){
});
root.innerHTML=ordered.map(p=>{const pnl=p.unrealized_pnl||0,col=catColor(p.category);
const title=`<a class="market-title" href="${esc(positionUrl(p))}" target="_blank" rel="noopener">${esc(p.question)}</a>`;
const positionRules=p.requires_complete_bundle
? `<div class="sub">Complete paired hedge: individual stops and gain sales are disabled; the two legs stay intact through settlement.</div>`
:p.maker_pair_pending
? `<div class="sub">Unmatched maker leg: the complementary resting bid remains live; unresolved inventory exits at an executable bid after ${MAKER_QUOTE_EXPIRY_HOURS}h.</div>`
:`<div class="sub">${esc(stopLossLabel(p))}</div><div class="sub">${esc(gainStopLabel(p))}</div>`;
return `<div class="pos"><div>
<div class="pq">${title}</div>
<div class="sub">${p.category?`<span class="cat-badge" style="background:${col}22;color:${col}">${esc(p.category)}</span> `:""}${p.shares} ${p.side} @ ${Math.round(p.entry_price*100)}¢ → ${Math.round(p.current_price*100)}¢</div>
${p.peer_note?`<div class="sub">Peer read: ${esc(p.peer_note)}</div>`:""}
${p.net_edge!=null||p.evidence_score!=null?`<div class="sub">Entry quality: ${p.net_edge!=null?`net edge ${((Math.abs(p.net_edge))*100).toFixed(1)}c`:"net edge n/a"} · ${p.evidence_score!=null?`evidence ${Math.round(Number(p.evidence_score||0)*100)}`:"evidence n/a"}</div>`:""}
${p.risk_budget_pct?`<div class="sub">Binary loss budget: max ${Number(p.risk_budget_pct).toFixed(1)}% of agent equity at entry</div>`:""}
<div class="sub">${esc(stopLossLabel(p))}</div>
<div class="sub">${esc(gainStopLabel(p))}</div>
${positionRules}
<div class="sub"><a class="market-link" href="${esc(positionUrl(p))}" target="_blank" rel="noopener">Open exact market</a></div>
</div><div class="right"><div>${fmtUSD(p.value)}</div><div class="${signClass(pnl)}">${pnl>=0?"+":""}${fmtUSD(pnl)}</div></div></div>`;}).join("");
}
@@ -4704,7 +4813,9 @@ 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,
pairedMakerQuotes:true,makerQuoteExpiryHours:MAKER_QUOTE_EXPIRY_HOURS,makerMinimumLockMarginPct:MAKER_MIN_LOCK_MARGIN*100,hypotheticalRewardsCredited:false,
pairedMakerQuotes:true,makerQuoteExpiryHours:MAKER_QUOTE_EXPIRY_HOURS,makerMinimumLockMarginPct:MAKER_MIN_LOCK_MARGIN*100,
makerTouchSource:"Polymarket CLOB batch price history",makerOutcomeLimit:MAKER_OUTCOME_LIMIT,makerMinimumCohortAttempts:MAKER_MIN_COHORT_ATTEMPTS,
makerMaximumLearningMultiplier:1.5,makerLosingCohortExplorationPct:20,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(){
@@ -4889,6 +5000,35 @@ function runEngineSelfTest(){
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 touchBook=defaultPortfolio();stageMakerQuotes(touchBook,[makerMarket]);const touchQuote=touchBook.maker_quotes[0];touchQuote.created_at=hoursAgo(1);touchQuote.yes_quote_updated_at=touchQuote.created_at;touchQuote.no_quote_updated_at=touchQuote.created_at;
const touchTimestamp=Math.floor((Date.now()-30*60000)/1000),touchMarket=market(Object.assign({},makerMarket,{best_bid:0.40,best_ask:0.44,yes_price:0.42,no_price:0.58}));
const touchFill=manageMakerQuotes(touchBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{yes:[{t:touchTimestamp,p:0.395}],no:[{t:touchTimestamp,p:0.58}]}});
const offlineTouchBook=defaultPortfolio();stageMakerQuotes(offlineTouchBook,[makerMarket]);offlineTouchBook.maker_quotes[0].created_at=hoursAgo(1);
const offlineTouch=manageMakerQuotes(offlineTouchBook,{"maker-test":touchMarket},{executeTrades:false,touchHistory:{yes:[{t:touchTimestamp,p:0.395}]}});
const repriceBook=defaultPortfolio();stageMakerQuotes(repriceBook,[makerMarket]);const repriceQuote=repriceBook.maker_quotes[0];repriceQuote.created_at=hoursAgo(2);
manageMakerQuotes(repriceBook,{"maker-test":market(Object.assign({},makerMarket,{best_bid:0.38,best_ask:0.40,yes_price:0.39,no_price:0.61}))},{executeTrades:true});
const preRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
const preRepriceIgnored=preRepriceTouch.fills===0&&repriceBook.positions.length===1&&repriceBook.maker_quotes.length===1;
repriceQuote.no_quote_updated_at=hoursAgo(1);
const postRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
const unfilledBook=defaultPortfolio();stageMakerQuotes(unfilledBook,[makerMarket]);unfilledBook.maker_quotes[0].created_at=hoursAgo(25);
const unfilledExpiry=manageMakerQuotes(unfilledBook,{"maker-test":touchMarket},{executeTrades:true});
const profitableMakerBook=defaultPortfolio(),losingMakerBook=defaultPortfolio();
for(let i=0;i<6;i++){
profitableMakerBook.maker_outcomes.push({quote_id:`profit-${i}`,market_id:`profit-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"locked",pnl:4,reserved_capital:100,strategy_version:SUGGESTION_ENGINE_VERSION});
losingMakerBook.maker_outcomes.push({quote_id:`loss-${i}`,market_id:`loss-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"single-exit",pnl:-4,reserved_capital:100,strategy_version:SUGGESTION_ENGINE_VERSION});
}
const profitableCandidate=makerCandidateLearning(makerPairCandidate(makerMarket),buildMakerProfile(profitableMakerBook));
let blockedMarket=null,explorationMarket=null;
for(let i=0;i<200&&(!blockedMarket||!explorationMarket);i++){
const id=`maker-learning-${i}`,candidate=makerPairCandidate(Object.assign({},makerMarket,{id})),lane=stableHash(`maker-explore:${id}`)%100;
if(lane>=20&&!blockedMarket)blockedMarket=candidate;if(lane<20&&!explorationMarket)explorationMarket=candidate;
}
const losingProfile=buildMakerProfile(losingMakerBook),blockedMakerCandidate=makerCandidateLearning(blockedMarket,losingProfile),explorationMakerCandidate=makerCandidateLearning(explorationMarket,losingProfile);
const makerDedupeBook=defaultPortfolio(),dedupeQuote=Object.assign({},makerQuote,{id:"dedupe-maker"});
recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);
const makerCompactionBook=defaultPortfolio();makerCompactionBook.maker_quotes=[Object.assign({},makerQuote)];makerCompactionBook.maker_outcomes=[...profitableMakerBook.maker_outcomes];
const compactedMakerBook=compactPortfolioForSync(makerCompactionBook);
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},
@@ -5076,8 +5216,20 @@ function runEngineSelfTest(){
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,
lockedPairRecordsLearningOutcome:makerBook.maker_outcomes.length===1&&makerBook.maker_outcomes[0].status==="locked"&&makerBook.maker_outcomes[0].pnl===makerExpectedProfit,
unmatchedInventoryExpiresAtExecutableBid:makerExpired.expired===1&&makerExpiryBook.positions.length===0&&makerExpiryBook.maker_quotes.length===0&&Math.abs(makerExpiryBook.cash-9997.5)<=0.02,
adverseExitRecordsNetLoss:makerExpiryBook.maker_outcomes.length===1&&makerExpiryBook.maker_outcomes[0].status==="single-exit"&&makerExpiryBook.maker_outcomes[0].pnl===-2.5,
unfilledExpiryRecordsZero:unfilledExpiry.expired===1&&unfilledBook.maker_outcomes.length===1&&unfilledBook.maker_outcomes[0].status==="unfilled"&&unfilledBook.maker_outcomes[0].pnl===0,
offlineSnapshotCannotInventFills:makerOffline.fills===0&&makerOfflineBook.positions.length===0&&makerOfflineBook.maker_quotes.length===1,
clobHistoryTouchCanFillRestingBid:touchFill.fills===1&&touchFill.historyTouches===1&&touchBook.positions.length===1&&touchBook.positions[0].entry_reason.includes("clob-price-history"),
offlineHistoryCannotInventFill:offlineTouch.fills===0&&offlineTouchBook.positions.length===0&&offlineTouchBook.maker_quotes.length===1,
repricedQuoteRejectsEarlierTouch:preRepriceIgnored,
repricedQuoteAcceptsLaterTouch:postRepriceTouch.fills===1&&postRepriceTouch.locked===1&&repriceBook.maker_quotes.length===0,
profitableCohortScalesWithinCap:profitableCandidate&&profitableCandidate.maker_learning_state==="promoted"&&profitableCandidate.units>makerQuote.units&&profitableCandidate.units<=makerQuote.units*1.5,
losingCohortRotatesOut:blockedMakerCandidate===null,
losingCohortKeepsBoundedExploration:explorationMakerCandidate&&explorationMakerCandidate.maker_learning_state==="exploration"&&explorationMakerCandidate.maker_learning_multiplier===1,
outcomesAreDeduplicated:makerDedupeBook.maker_outcomes.length===1,
makerLearningSurvivesCompaction:compactedMakerBook.maker_quotes.length===1&&compactedMakerBook.maker_outcomes.length===6,
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,
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = "polymarket-arena-build-63";
const CACHE_NAME = "polymarket-arena-build-64";
const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"];
self.addEventListener("install", event => {