Block audited losing signal cohorts

This commit is contained in:
Theodore Song
2026-08-18 12:36:12 -04:00
parent 846d1f1482
commit 25622d2ee5
6 changed files with 208 additions and 52 deletions
+32 -17
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 The site fetches live Polymarket markets, generates agent suggestions, lets you
run frequent paper cycles, and syncs the shared arena state through Neon or run frequent paper cycles, and syncs the shared arena state through Neon or
Vercel Blob. Build 47 also installs an offline app shell and caches timestamped Vercel Blob. Build 48 also installs an offline app shell and caches timestamped
market snapshots. During an outage, cycles continue locally; cached entries are market snapshots. During an outage, cycles continue locally; cached entries are
allowed for 90 minutes, older snapshots become mark-only, and all cached data allowed for 90 minutes, older snapshots become mark-only, and all cached data
expires after 24 hours. expires after 24 hours.
@@ -27,7 +27,7 @@ The learner shrinks small samples toward neutral, caps sizing changes to
15% of candidates for deterministic exploration so a stale regime cannot become 15% of candidates for deterministic exploration so a stale regime cannot become
permanent. permanent.
Strategy 44 treats each binary stake as capable of falling to zero even when the Strategy 45 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 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, aggressive positions at 3%-5%, with lower limits for near-term, extreme-price,
reversal, and fast-moving setups. Oversized positions inherited from older reversal, and fast-moving setups. Oversized positions inherited from older
@@ -65,9 +65,17 @@ negative in all three chronological segments. Reversals averaged -3.69%, with a
market-clustered 90% interval entirely below zero. Crypto and Sports were also 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 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 -0.82% row mean and +1.31% market mean, with no rule robustly negative across all
segments. Strategy 44 therefore disables reversal entries, retains their signals segments. Strategy 45 therefore keeps reversal entries observation-only until
for paper grading, and evaluates adaptation at 24 hours. It does not promote any their recent signal and quality cohorts independently earn promotion, retains
rule because no positive cohort passed the same robustness checks. their signals for paper grading, and evaluates adaptation at 24 hours.
The expanded active-market audit loaded history for 498 of the top 500 active
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 45 therefore
keeps Crypto and Sports trends observation-only while continuing to grade them.
A corrected 200-market audit paged through 197 markets with usable history and A corrected 200-market audit paged through 197 markets with usable history and
1,912 twelve-hour outcomes. Reversals remained negative in every chronological 1,912 twelve-hour outcomes. Reversals remained negative in every chronological
@@ -75,23 +83,23 @@ 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 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 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. still crossed zero; that supports a longer hold test, not a larger entry bet.
Strategy 44 gives Politics trend positions that 72-hour observation window before Strategy 45 gives Politics trend positions that 72-hour observation window before
ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget
reductions remain immediate. reductions remain immediate.
Strategy 44 also subtracts a half-cent round-trip cost when grading each live Strategy 45 also subtracts a half-cent round-trip cost when grading each live
walk-forward signal. Confidence uses the largest independent matching bucket, walk-forward signal. Confidence uses the largest independent matching bucket,
not the sum of five overlapping feature buckets, and evidence from older engine not the sum of five overlapping feature buckets, and evidence from older engine
versions is down-weighted. This prevents a handful of duplicated observations versions is down-weighted. This prevents a handful of duplicated observations
from authorizing larger positions or hiding a modest negative regime. from authorizing larger positions or hiding a modest negative regime.
Strategy 44 adds uncertainty-aware promotion and demotion. A matching setup must Strategy 45 adds uncertainty-aware promotion and demotion. A matching setup must
accumulate at least eight effective observations and agree across at least two accumulate at least eight effective observations and agree across at least two
feature views before repeatable positive evidence can increase size or repeatable feature views before repeatable positive evidence can increase size or repeatable
negative evidence can block a new entry. Mixed evidence stays close to neutral negative evidence can block a new entry. Mixed evidence stays close to neutral
instead of being mistaken for an edge. instead of being mistaken for an edge.
Build 47 enforces the documented offline boundary end to end. Cached snapshots Build 48 enforces the documented offline boundary end to end. Cached snapshots
under 90 minutes old may continue paper execution. Older snapshots remain usable 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, 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 stop-losses, gain-stops, risk rebalances, settlements, or policy exits. Network
@@ -104,17 +112,17 @@ 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 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. records are migrated into the same strategy lineage without losing evidence.
Build 47 independently refreshes markets for matured pending signals that have Build 48 independently refreshes markets for matured pending signals that have
left the current top-500 activity scan. Unavailable markets remain queued for a left the current top-500 activity scan. Unavailable markets remain queued for a
bounded retry window. This prevents activity-rank survivorship from deciding bounded retry window. This prevents activity-rank survivorship from deciding
which wins and losses reach the adaptive calibration ledger. which wins and losses reach the adaptive calibration ledger.
Strategy 44 coordinates high-risk exploration globally. Crypto, near-term, Strategy 45 coordinates high-risk exploration globally. Near-term, extreme-price,
extreme-price, legacy reversal, and other gap-prone positions may be held materially by and other gap-prone positions may be held materially by only one agent, while
only one agent, while ordinary independently confirmed markets retain the ordinary independently confirmed markets retain the two-agent cap. The robustly
two-agent cap. A historically blocked Sports trend can enter the exploration lane negative Sports- and Crypto-trend cohorts cannot enter through exploration.
for only one designated agent, preventing duplicated speculative losses. Reversal Reversal and short-dated NO signals remain observation-only until their own recent
signals cannot enter that lane and remain observation-only. feature cohorts pass the promotion gate.
Run `npm run evaluate:settlements` to evaluate fixed decisions made 1, 3, 7, Run `npm run evaluate:settlements` to evaluate fixed decisions made 1, 3, 7,
14, 30, and 90 days before known binary settlements. The audit uses one 14, 30, and 90 days before known binary settlements. The audit uses one
@@ -126,6 +134,13 @@ cohort robust. Environment variables beginning with
`SETTLEMENT_` control its market count, concurrency, horizons, and cost. Set `SETTLEMENT_` control its market count, concurrency, horizons, and cost. Set
`SETTLEMENT_SUMMARY=1` for the compact report. `SETTLEMENT_SUMMARY=1` for the compact report.
Run `npm run evaluate:neg-risk` to scan complete active negative-risk events for
whole-event YES or NO bundles using executable best asks/bids, per-leg costs, and
a minimum-liquidity requirement. The 500-event audit found 33 complete liquid
negative-risk events and zero positive worst-case bundle returns after costs.
Midpoint price sums sometimes looked attractive, but executable spreads removed
the apparent edge, so Strategy 45 does not pretend those snapshots are arbitrage.
The expanded event-clustered run loaded history for 498 of the 500 highest-volume The expanded event-clustered run loaded history for 498 of the 500 highest-volume
resolved markets with no fetch failures. No side, price band, category, trend, resolved markets with no fetch failures. No side, price band, category, trend,
or 1-90 day holding rule passed the required train/test confidence checks. In or 1-90 day holding rule passed the required train/test confidence checks. In
@@ -135,7 +150,7 @@ static settlement-direction boost from this audit.
The earlier 200-resolved-market audit found short-dated NO entries strongly 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 negative, but the 500-market rerun did not reproduce that loss in its newer test
segment. Strategy 44 therefore treats the result as a provisional prior instead segment. Strategy 45 therefore treats the result as a provisional prior instead
of a permanent ban: NO entries with 21 days or less remain observation-only until 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 the recent walk-forward calibration promotes their matching side and duration
cohorts. Exact numeric-range contracts are excluded from new entries because a cohorts. Exact numeric-range contracts are excluded from new entries because a
+69 -30
View File
@@ -341,7 +341,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
<nav class="topnav"> <nav class="topnav">
<div class="brand"> <div class="brand">
<div class="logo">🏆</div> <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 44 · build 47</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 45 · build 48</div></div>
</div> </div>
<div class="tabs" id="tabs"> <div class="tabs" id="tabs">
<button class="tab" data-tab="overview">Overview</button> <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"> <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. <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>
<div class="live-build-banner"><b>Build 47 active:</b> exact-range contracts are excluded from agent entries because their binary settlement jumps can bypass stops. Short-dated NO signals remain observation-only until recent walk-forward evidence earns promotion, while all confirmed signals now train the adaptive ledger even when no portfolio buys them. This remains paper trading; profits are not guaranteed.</div> <div class="live-build-banner"><b>Build 48 active:</b> Crypto and Sports trends are observation-only after a 500-market walk-forward audit found repeatable 24-hour losses. Reversal and short-dated NO signals can return only after their own recent cohorts earn promotion. Exact-range contracts remain excluded because settlement jumps can bypass stops. This remains paper trading; profits are not guaranteed.</div>
<!-- ============ OVERVIEW ============ --> <!-- ============ OVERVIEW ============ -->
<section class="tabpanel" data-tab="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> </section>
<footer> <footer>
Build 47 · Adaptive strategy 44 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice · Build 48 · Adaptive strategy 45 · 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> <a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
</footer> </footer>
</div> </div>
@@ -774,9 +774,9 @@ const POLITICS_TREND_MIN_HOLD_HOURS = 72;
const EXIT_CONFIRM_HOURS = 6; const EXIT_CONFIRM_HOURS = 6;
const AGENTS_KEY = "pma_agents_v2"; const AGENTS_KEY = "pma_agents_v2";
const SUG_KEY = "pma_suggestions_v5"; const SUG_KEY = "pma_suggestions_v5";
const BUILD_VERSION = 47; const BUILD_VERSION = 48;
const SUGGESTION_ENGINE_VERSION = 44; const SUGGESTION_ENGINE_VERSION = 45;
const PREVIOUS_STRATEGY_VERSION = 43; const PREVIOUS_STRATEGY_VERSION = 44;
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40}); const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
function normalizedStrategyVersion(value){ function normalizedStrategyVersion(value){
const version=Number(value||0); const version=Number(value||0);
@@ -1234,12 +1234,16 @@ function analyzeMarket(m,realWorldSignals={}){
&&absNet>=MIN_LIQUIDITY_EDGE&&signal.confidence>=0.68&&evidence.score>=0.50&&m.spread<=0.018&&m.liquidity>=5000; &&absNet>=MIN_LIQUIDITY_EDGE&&signal.confidence>=0.68&&evidence.score>=0.50&&m.spread<=0.018&&m.liquidity>=5000;
const trendTradeReady=!strictTradeReady&&!catalystTradeReady&&!reversalTradeReady&&!liquidityTradeReady&&commonReady&&signal.type==="trend" const trendTradeReady=!strictTradeReady&&!catalystTradeReady&&!reversalTradeReady&&!liquidityTradeReady&&commonReady&&signal.type==="trend"
&&absNet>=Math.max(MIN_TREND_EDGE,policy.minEdge)&&signal.confidence>=0.60&&evidence.score>=policy.minEvidence; &&absNet>=Math.max(MIN_TREND_EDGE,policy.minEdge)&&signal.confidence>=0.60&&evidence.score>=policy.minEvidence;
const tradeReady=signal.type!=="reversal"&&(strictTradeReady||catalystTradeReady||liquidityTradeReady||trendTradeReady); const entryCandidate=strictTradeReady||catalystTradeReady||reversalTradeReady||liquidityTradeReady||trendTradeReady;
const auditedObservationOnly=signal.type==="reversal"||(signal.type==="trend"&&["Sports","Crypto"].includes(m.category));
const tradeReady=!auditedObservationOnly&&entryCandidate;
const quality=signal.type==="reversal"?"reversal":(strictTradeReady?"confirmed":(catalystTradeReady?"catalyst":(liquidityTradeReady?"liquid-trend":(trendTradeReady?"trend":"watch")))); const quality=signal.type==="reversal"?"reversal":(strictTradeReady?"confirmed":(catalystTradeReady?"catalyst":(liquidityTradeReady?"liquid-trend":(trendTradeReady?"trend":"watch"))));
const side=signal.side||((Number(m.price_change_1d||0)>=0)?"YES":"NO"); const side=signal.side||((Number(m.price_change_1d||0)>=0)?"YES":"NO");
const entry=side==="YES"?p:m.no_price; const entry=side==="YES"?p:m.no_price;
let rationale; let rationale;
if(signal.type==="reversal"){rationale=`Observation only: the hourly move opposes the one-day direction, but independent audits found this reversal rule lost after costs. The signal will be graded without risking portfolio cash.`;} if(signal.type==="reversal"){rationale=`Observation only: the hourly move opposes the one-day direction, but independent audits found this reversal rule lost after costs. The signal will be graded without risking portfolio cash.`;}
else if(signal.type==="trend"&&m.category==="Crypto"){rationale="Observation only: the 500-market walk-forward audit found Crypto trends lost after costs at both 24 and 72 hours. This signal keeps training the ledger without risking portfolio cash.";}
else if(signal.type==="trend"&&m.category==="Sports"){rationale="Observation only: the 500-market walk-forward audit found Sports trends lost after costs at 24 hours across every chronological segment. This signal keeps training the ledger without risking portfolio cash.";}
else if(strictTradeReady){rationale=`Confirmed setup: ${side} has ${Math.round(signal.strength*100)} signal strength across independent time windows and a ${(absNet*100).toFixed(1)}c signal margin after friction and category uncertainty.`;} else if(strictTradeReady){rationale=`Confirmed setup: ${side} has ${Math.round(signal.strength*100)} signal strength across independent time windows and a ${(absNet*100).toFixed(1)}c signal margin after friction and category uncertainty.`;}
else if(catalystTradeReady){rationale=`Catalyst-confirmed setup: ${side} price action agrees across the required windows, recent outside coverage exists, and ${(absNet*100).toFixed(1)}c of estimated movement remains after friction. News coverage confirms activity, not direction.`;} else if(catalystTradeReady){rationale=`Catalyst-confirmed setup: ${side} price action agrees across the required windows, recent outside coverage exists, and ${(absNet*100).toFixed(1)}c of estimated movement remains after friction. News coverage confirms activity, not direction.`;}
else if(reversalTradeReady){rationale=`Confirmed reversal: the one-day move is reversing in the hourly window with adequate liquidity and a ${(absNet*100).toFixed(1)}c post-friction signal margin.`;} else if(reversalTradeReady){rationale=`Confirmed reversal: the one-day move is reversing in the hourly window with adequate liquidity and a ${(absNet*100).toFixed(1)}c post-friction signal margin.`;}
@@ -1263,7 +1267,8 @@ function analyzeMarket(m,realWorldSignals={}){
net_edge:netEdge,friction:+friction.toFixed(4),chase_penalty:+chase.toFixed(4),evidence_score:+evidence.score.toFixed(2),evidence_source_count:evidence.source_count||0, net_edge:netEdge,friction:+friction.toFixed(4),chase_penalty:+chase.toFixed(4),evidence_score:+evidence.score.toFixed(2),evidence_source_count:evidence.source_count||0,
quality,side,entry_price:+entry.toFixed(4),conviction,volume:m.volume,volume_24hr:m.volume_24hr,liquidity:m.liquidity, quality,side,entry_price:+entry.toFixed(4),conviction,volume:m.volume,volume_24hr:m.volume_24hr,liquidity:m.liquidity,
spread:m.spread,price_change_1h:m.price_change_1h,price_change_1d:m.price_change_1d,price_change_1w:m.price_change_1w, spread:m.spread,price_change_1h:m.price_change_1h,price_change_1d:m.price_change_1d,price_change_1w:m.price_change_1w,
momentum_strength:+signal.strength.toFixed(2),signal_strength:+signal.strength.toFixed(2),signal_confidence:+signal.confidence.toFixed(2),signal_type:signal.type,trade_ready:tradeReady,jump_risk:jumpRisk, momentum_strength:+signal.strength.toFixed(2),signal_strength:+signal.strength.toFixed(2),signal_confidence:+signal.confidence.toFixed(2),signal_type:signal.type,
trade_ready:tradeReady,entry_candidate:entryCandidate,audited_observation_only:auditedObservationOnly,jump_risk:jumpRisk,
days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers,rationale}; days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers,rationale};
} }
const pct=(x)=>Math.round(x*100)+"%"; const pct=(x)=>Math.round(x*100)+"%";
@@ -1290,7 +1295,7 @@ function marketWatchSuggestion(m,realWorldSignals={}){
yes_price:p,no_price:m.no_price,fair_value:+fair.toFixed(4),edge:+edgeYes.toFixed(4), yes_price:p,no_price:m.no_price,fair_value:+fair.toFixed(4),edge:+edgeYes.toFixed(4),
net_edge:netEdge,friction:+friction.toFixed(4),chase_penalty:+chase.toFixed(4),evidence_score:+evidence.score.toFixed(2),quality:"watch", net_edge:netEdge,friction:+friction.toFixed(4),chase_penalty:+chase.toFixed(4),evidence_score:+evidence.score.toFixed(2),quality:"watch",
side,entry_price:+entry.toFixed(4),conviction,volume:m.volume,volume_24hr:m.volume_24hr,liquidity:m.liquidity, side,entry_price:+entry.toFixed(4),conviction,volume:m.volume,volume_24hr:m.volume_24hr,liquidity:m.liquidity,
trade_ready:false,watch_only:true, trade_ready:false,entry_candidate:false,audited_observation_only:false,watch_only:true,
days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers, days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers,
rationale:`Market watchlist: included for coverage from the active-market scan. Signal margin is ${(Math.abs(netEdge)*100).toFixed(1)}c and evidence score is ${Math.round(evidence.score*100)}, so agents will not buy it unless confirmation improves.`}; rationale:`Market watchlist: included for coverage from the active-market scan. Signal margin is ${(Math.abs(netEdge)*100).toFixed(1)}c and evidence score is ${Math.round(evidence.score*100)}, so agents will not buy it unless confirmation improves.`};
} }
@@ -1396,7 +1401,8 @@ function compactSuggestionForSync(s){
conviction:s.conviction,volume:s.volume,volume_24hr:s.volume_24hr,liquidity:s.liquidity, conviction:s.conviction,volume:s.volume,volume_24hr:s.volume_24hr,liquidity:s.liquidity,
spread:s.spread,price_change_1h:s.price_change_1h,price_change_1d:s.price_change_1d,price_change_1w:s.price_change_1w,momentum_strength:s.momentum_strength, spread:s.spread,price_change_1h:s.price_change_1h,price_change_1d:s.price_change_1d,price_change_1w:s.price_change_1w,momentum_strength:s.momentum_strength,
signal_strength:s.signal_strength,signal_confidence:s.signal_confidence,signal_type:s.signal_type, signal_strength:s.signal_strength,signal_confidence:s.signal_confidence,signal_type:s.signal_type,
trade_ready:s.trade_ready,watch_only:s.watch_only,jump_risk:s.jump_risk, trade_ready:s.trade_ready,entry_candidate:s.entry_candidate,audited_observation_only:s.audited_observation_only,
adaptive_promotion:s.adaptive_promotion,watch_only:s.watch_only,jump_risk:s.jump_risk,
days_to_resolution:s.days_to_resolution,drivers:s.drivers,rationale:s.rationale, days_to_resolution:s.days_to_resolution,drivers:s.drivers,rationale:s.rationale,
}; };
} }
@@ -1772,18 +1778,35 @@ function stableExploration(agentId,marketId){
} }
function historicalOpportunityPrior(s){ function historicalOpportunityPrior(s){
const rows=[]; const rows=[];
if((s.signal_type||"")==="reversal")rows.push({feature:"reversal",score:-0.0369,samples:207,blocked:true,hardBlocked:true}); if((s.signal_type||"")==="reversal")rows.push({feature:"reversal",score:-0.0369,samples:207,blocked:true,requiresPromotion:true,
if((s.side||"")==="NO"&&Number(s.days_to_resolution)<=21)rows.push({feature:"short-no",score:-0.3733,samples:64,blocked:true,requiresPromotion:true}); requiredPromotionFeatures:["signal:reversal","quality:reversal"]});
if((s.signal_type||"")==="trend"&&(s.category||"Other")==="Sports")rows.push({feature:"sports-trend",score:-0.0353,samples:162,blocked:true}); if((s.side||"")==="NO"&&Number(s.days_to_resolution)<=21)rows.push({feature:"short-no",score:-0.3733,samples:64,blocked:true,requiresPromotion:true,
requiredPromotionFeatures:["side:NO","duration:short"]});
if((s.signal_type||"")==="trend"&&(s.category||"Other")==="Sports")rows.push({feature:"sports-trend",score:-0.0544,samples:307,blocked:true,hardBlocked:true});
if((s.signal_type||"")==="trend"&&(s.category||"Other")==="Crypto")rows.push({feature:"crypto-trend",score:-0.0383,samples:341,blocked:true,hardBlocked:true});
if((s.category||"Other")==="Crypto")rows.push({feature:"crypto",score:-0.0344,samples:140,blocked:false}); if((s.category||"Other")==="Crypto")rows.push({feature:"crypto",score:-0.0344,samples:140,blocked:false});
if(entryBand(s.entry_price)==="longshot")rows.push({feature:"longshot",score:-0.0327,samples:277,blocked:false}); if(entryBand(s.entry_price)==="longshot")rows.push({feature:"longshot",score:-0.0327,samples:277,blocked:false});
if((s.side||"")==="YES")rows.push({feature:"yes-side",score:-0.0190,samples:954,blocked:false}); if((s.side||"")==="YES")rows.push({feature:"yes-side",score:-0.0190,samples:954,blocked:false});
if(!rows.length)return {score:0,confidence:0,multiplier:1,blocked:false,hardBlocked:false,requiresPromotion:false,features:[]}; if(!rows.length)return {score:0,confidence:0,multiplier:1,blocked:false,hardBlocked:false,requiresPromotion:false,requiredPromotionFeatures:[],features:[]};
const score=rows.reduce((sum,row)=>sum+row.score,0)/rows.length; const score=rows.reduce((sum,row)=>sum+row.score,0)/rows.length;
const samples=Math.max(...rows.map(row=>row.samples)),confidence=samples/(samples+200); const samples=Math.max(...rows.map(row=>row.samples)),confidence=samples/(samples+200);
return {score:+score.toFixed(4),confidence:+confidence.toFixed(3), return {score:+score.toFixed(4),confidence:+confidence.toFixed(3),
multiplier:+clamp(1+score*confidence*2.5,0.82,1).toFixed(3),blocked:rows.some(row=>row.blocked), multiplier:+clamp(1+score*confidence*2.5,0.82,1).toFixed(3),blocked:rows.some(row=>row.blocked),
hardBlocked:rows.some(row=>row.hardBlocked),requiresPromotion:rows.some(row=>row.requiresPromotion),features:rows.map(row=>row.feature)}; hardBlocked:rows.some(row=>row.hardBlocked),requiresPromotion:rows.some(row=>row.requiresPromotion),
requiredPromotionFeatures:[...new Set(rows.flatMap(row=>row.requiredPromotionFeatures||[]))],features:rows.map(row=>row.feature)};
}
function historicalPromotionMet(historical,calibrationModel){
return !historical.requiresPromotion||historical.requiredPromotionFeatures.every(key=>{
const row=calibrationModel&&calibrationModel.buckets&&calibrationModel.buckets[key];
return Number(row&&row.weight||0)>=8&&Number(row&&row.lower_bound||0)>0.003;
});
}
function applyAdaptiveMarketPromotion(s,calibrationModel){
if(!s||s.trade_ready||!s.entry_candidate)return s;
const historical=historicalOpportunityPrior(s);
if(historical.hardBlocked||!historical.requiresPromotion||!historicalPromotionMet(historical,calibrationModel))return s;
return Object.assign({},s,{trade_ready:true,watch_only:false,audited_observation_only:false,adaptive_promotion:true,
rationale:`Adaptive promotion: this ${s.signal_type||"signal"} remained observation-only until its recent independent cohorts accumulated enough positive net-of-cost evidence. ${s.rationale||""}`});
} }
function learnedOpportunity(cfg,p,s,profile=null,calibration=null){ function learnedOpportunity(cfg,p,s,profile=null,calibration=null){
const model=profile||buildAdaptiveProfile(p),features=learningFeatures(s),rows=features.map(k=>model.buckets[k]).filter(Boolean); const model=profile||buildAdaptiveProfile(p),features=learningFeatures(s),rows=features.map(k=>model.buckets[k]).filter(Boolean);
@@ -1792,10 +1815,7 @@ function learnedOpportunity(cfg,p,s,profile=null,calibration=null){
const confidence=independentWeight/(independentWeight+12),exploration=stableExploration(cfg.id,s.market_id); const confidence=independentWeight/(independentWeight+12),exploration=stableExploration(cfg.id,s.market_id);
const calibrationModel=calibration||{samples:0,buckets:{}},market=calibratedOpportunity(s,calibrationModel); const calibrationModel=calibration||{samples:0,buckets:{}},market=calibratedOpportunity(s,calibrationModel);
const historical=historicalOpportunityPrior(s); const historical=historicalOpportunityPrior(s);
const historicalProofMet=!historical.requiresPromotion||["side:NO","duration:short"].every(key=>{ const historicalProofMet=historicalPromotionMet(historical,calibrationModel);
const row=calibrationModel.buckets&&calibrationModel.buckets[key];
return Number(row&&row.weight||0)>=8&&Number(row&&row.lower_bound||0)>0.003;
});
const positiveRows=rows.filter(r=>Number(r.weight||0)>=6&&Number(r.lower_bound||0)>0.005); const positiveRows=rows.filter(r=>Number(r.weight||0)>=6&&Number(r.lower_bound||0)>0.005);
const negativeRows=rows.filter(r=>Number(r.weight||0)>=6&&Number(r.upper_bound||0)<-0.01); const negativeRows=rows.filter(r=>Number(r.weight||0)>=6&&Number(r.upper_bound||0)<-0.01);
const personalPromoted=positiveRows.length>=2&&negativeRows.length===0; const personalPromoted=positiveRows.length>=2&&negativeRows.length===0;
@@ -2236,10 +2256,11 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
const targetExposure=clamp(Number(d.targetExposure??cfg.targetExposure??0.62),0,1); const targetExposure=clamp(Number(d.targetExposure??cfg.targetExposure??0.62),0,1);
const minExposure=0,belowFloor=false; const minExposure=0,belowFloor=false;
const tradeReadyCount=rankedSugs.filter(s=>s.trade_ready).length; const tradeReadyCount=rankedSugs.filter(s=>s.trade_ready).length;
const strategyMatches=rankedSugs.filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)); const entryCandidateCount=rankedSugs.filter(s=>s.trade_ready||s.entry_candidate).length;
const strategyMatches=rankedSugs.filter(s=>(s.trade_ready||s.entry_candidate)&&agentAcceptsSuggestion(cfg,s));
const strategyCount=strategyMatches.length; const strategyCount=strategyMatches.length;
if((p.positions||[]).length>=maxPositions||d.maxNew<=0){ if((p.positions||[]).length>=maxPositions||d.maxNew<=0){
p.lastDecision=Object.assign({},d,{currentExposure:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:+minExposure.toFixed(3),tradeReadyCount,strategyCandidates:strategyCount,eligibleCandidates:0,opened:0, p.lastDecision=Object.assign({},d,{currentExposure:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:+minExposure.toFixed(3),tradeReadyCount,entryCandidateCount,strategyCandidates:strategyCount,eligibleCandidates:0,opened:0,
rejectionCounts:{portfolio_limit:strategyCount},allocationStatus:d.maxNew<=0?"New entries are paused by the daily drawdown limit.":`The ${maxPositions}-position portfolio limit is full.`}); rejectionCounts:{portfolio_limit:strategyCount},allocationStatus:d.maxNew<=0?"New entries are paused by the daily drawdown limit.":`The ${maxPositions}-position portfolio limit is full.`});
return []; return [];
} }
@@ -2333,7 +2354,7 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
else if(!cands.length)allocationStatus=`${strategyCount} strategy matches were blocked by confidence, overlap, cooldown, or focus rules.`; else if(!cands.length)allocationStatus=`${strategyCount} strategy matches were blocked by confidence, overlap, cooldown, or focus rules.`;
else allocationStatus="Confirmed candidates existed, but position, overlap, category, reserve, or minimum-size limits blocked an entry."; else allocationStatus="Confirmed candidates existed, but position, overlap, category, reserve, or minimum-size limits blocked an entry.";
p.lastDecision=Object.assign({},d,{currentExposure:+exposureAfter.toFixed(3),exposureBefore:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:0,belowFloor:false,capacityAvailable:exposureAfter+0.001<targetExposure, p.lastDecision=Object.assign({},d,{currentExposure:+exposureAfter.toFixed(3),exposureBefore:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:0,belowFloor:false,capacityAvailable:exposureAfter+0.001<targetExposure,
tradeReadyCount,strategyCandidates:strategyCount,eligibleCandidates:cands.length,opened,rejectionCounts,allocationStatus}); tradeReadyCount,entryCandidateCount,strategyCandidates:strategyCount,eligibleCandidates:cands.length,opened,rejectionCounts,allocationStatus});
return openedIds; return openedIds;
} }
function recordSnapshot(p){ function recordSnapshot(p){
@@ -2497,6 +2518,11 @@ async function runDailyCycle(){
updateSignalLedger(st,[...analysisMarkets,...supplemental],sugs); updateSignalLedger(st,[...analysisMarkets,...supplemental],sugs);
} }
const marketLearning=buildSignalCalibration(st.signal_ledger); const marketLearning=buildSignalCalibration(st.signal_ledger);
if(runMode==="live"){
sugs=sugs.map(s=>applyAdaptiveMarketPromotion(s,marketLearning));
saveSuggestions(sugs,markets.length,analysisMarkets.length);
saveMarketCache(analysisMarkets,sugs,priceMap);
}
setStatus("ten strategy agents trading…",true); setStatus("ten strategy agents trading…",true);
const preBoard=AGENTS.map(a=>({id:a.id,eq:equity(st.agents[a.id])})).sort((x,y)=>y.eq-x.eq); const preBoard=AGENTS.map(a=>({id:a.id,eq:equity(st.agents[a.id])})).sort((x,y)=>y.eq-x.eq);
const leaderEq=preBoard[0]?preBoard[0].eq:STARTING_BALANCE; const leaderEq=preBoard[0]?preBoard[0].eq:STARTING_BALANCE;
@@ -2508,7 +2534,7 @@ async function runDailyCycle(){
} }
if(executeTrades)reduceStrategyOverlap(st); if(executeTrades)reduceStrategyOverlap(st);
const entriesAllowed=runMode==="live"||cachePolicy.entriesAllowed; const entriesAllowed=runMode==="live"||cachePolicy.entriesAllowed;
const cycleSuggestions=entriesAllowed?sugs:sugs.map(s=>Object.assign({},s,{trade_ready:false,watch_only:true})); const cycleSuggestions=entriesAllowed?sugs:sugs.map(s=>Object.assign({},s,{trade_ready:false,entry_candidate:false,watch_only:true}));
const claimedMarkets=new Set(); const claimedMarkets=new Set();
for(const cfg of strategyExecutionOrder(st)){ for(const cfg of strategyExecutionOrder(st)){
const p=st.agents[cfg.id]; const p=st.agents[cfg.id];
@@ -2696,12 +2722,12 @@ function decisionSummary(p){
const emotion=d.emotion?` Emotion: ${d.emotion}${d.urgency!=null?` (${Math.round(d.urgency*100)}% urgency)`:""}.`:""; const emotion=d.emotion?` Emotion: ${d.emotion}${d.urgency!=null?` (${Math.round(d.urgency*100)}% urgency)`:""}.`:"";
const exposure=d.currentExposure!=null&&d.targetExposure!=null?` Exposure ${Math.round(d.currentExposure*100)}%; ceiling ${Math.round(d.targetExposure*100)}%.`:""; const exposure=d.currentExposure!=null&&d.targetExposure!=null?` Exposure ${Math.round(d.currentExposure*100)}%; ceiling ${Math.round(d.targetExposure*100)}%.`:"";
const allocation=d.allocationStatus?` ${d.allocationStatus}`:""; const allocation=d.allocationStatus?` ${d.allocationStatus}`:"";
const candidates=d.tradeReadyCount!=null?` Candidate audit: ${d.tradeReadyCount} trade-ready, ${d.strategyCandidates||0} strategy matches, ${d.eligibleCandidates||0} fully eligible, ${d.opened||0} opened.`:""; const candidates=d.tradeReadyCount!=null?` Candidate audit: ${d.tradeReadyCount} globally trade-ready, ${d.entryCandidateCount||d.tradeReadyCount||0} technical candidates, ${d.strategyCandidates||0} strategy matches, ${d.eligibleCandidates||0} fully eligible, ${d.opened||0} opened.`:"";
const blockerLabels={historical_prior:"history-tested losing setup",learning:"live learned losing regime",confidence:"confidence",overlap:"material overlap",already_held:"already held",cooldown:"stop cooldown",focus:"category focus",price:"entry price",edge:"edge",timing:"timing",liquidity:"liquidity",activity:"activity",evidence:"evidence",direction:"direction",portfolio_limit:"portfolio limit"}; const blockerLabels={historical_prior:"history-tested losing setup",learning:"live learned losing regime",confidence:"confidence",overlap:"material overlap",already_held:"already held",cooldown:"stop cooldown",focus:"category focus",price:"entry price",edge:"edge",timing:"timing",liquidity:"liquidity",activity:"activity",evidence:"evidence",direction:"direction",portfolio_limit:"portfolio limit"};
const blockerRows=Object.entries(d.rejectionCounts||{}).filter(([,count])=>count>0).sort((a,b)=>b[1]-a[1]); const blockerRows=Object.entries(d.rejectionCounts||{}).filter(([,count])=>count>0).sort((a,b)=>b[1]-a[1]);
const blockers=blockerRows.length?` Blocks: ${blockerRows.slice(0,4).map(([key,count])=>`${blockerLabels[key]||key} ${count}`).join(", ")}.`:""; const 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 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 signals graded after ${SIGNAL_EVAL_HOURS} hours (${d.marketLearning.current_samples||0} under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future price${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired ungraded`:""}; ${d.marketLearning.promoted_buckets||0} feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Matured markets are repriced even after leaving the active scan, and confirmed observation-only signals also train the ledger. Confidence counts independent outcomes once and uncertainty gates sizing. Historical prior: reversal remains disabled; short-dated NO requires recent promotion; sports trends require the fixed exploration lane; exact-range contracts are excluded; crypto, longshots, and YES entries are sized down. Politics trends receive 72 hours before ordinary signal exits.`:""; const calibration=d.marketLearning?` Walk-forward calibration: ${d.marketLearning.samples||0} net-of-cost signals graded after ${SIGNAL_EVAL_HOURS} hours (${d.marketLearning.current_samples||0} under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future price${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired ungraded`:""}; ${d.marketLearning.promoted_buckets||0} feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Matured markets are repriced even after leaving the active scan, and confirmed observation-only signals also train the ledger. Confidence counts independent outcomes once and uncertainty gates sizing. Historical prior: Sports and Crypto trends stay observation-only; reversal and short-dated NO require promotion in their own recent cohorts; exact-range contracts are excluded; longshots and YES entries are sized down. Politics trends receive 72 hours before ordinary signal exits.`:"";
return `${d.mode} mode: ${d.reason}${emotion} Limits now: ${d.maxNew} new trade${d.maxNew===1?"":"s"}, max ${(d.maxFrac*100).toFixed(1)}% per position${d.minConv?`, conviction ${d.minConv}+`:""}.${learning}${calibration}${exposure}${allocation}${candidates}${blockers}`; return `${d.mode} mode: ${d.reason}${emotion} Limits now: ${d.maxNew} new trade${d.maxNew===1?"":"s"}, max ${(d.maxFrac*100).toFixed(1)}% per position${d.minConv?`, conviction ${d.minConv}+`:""}.${learning}${calibration}${exposure}${allocation}${candidates}${blockers}`;
} }
function renderAgentBrief(cfg,p,st){ function renderAgentBrief(cfg,p,st){
@@ -2905,7 +2931,7 @@ function renderSuggestions(){
const scanned=Number(data.market_count||0).toLocaleString(); const scanned=Number(data.market_count||0).toLocaleString();
const analyzed=Number(data.analyzed_count||data.market_count||0).toLocaleString(); const analyzed=Number(data.analyzed_count||data.market_count||0).toLocaleString();
$("focusNote").textContent=focus==="All" $("focusNote").textContent=focus==="All"
? `Loaded the ${scanned} most active markets, analyzed ${analyzed} by activity, liquidity, spread, and independent time-window confirmation, and kept ${all.length} ideas (${buyCount} BUY, ${watchCount} WATCH). Showing ${filtered.length}; BUY requires a confirmed trend, reversal, liquid setup, or catalyst with positive margin after modeled friction. Cash is retained when no setup qualifies. Avg evidence ${evAvg}.` ? `Loaded the ${scanned} most active markets, analyzed ${analyzed} by activity, liquidity, spread, and independent time-window confirmation, and kept ${all.length} ideas (${buyCount} BUY, ${watchCount} WATCH). Showing ${filtered.length}; BUY requires a confirmed non-Crypto, non-Sports trend or catalyst with positive margin after modeled friction. Reversals and audited losing cohorts remain WATCH while they train. Cash is retained when no setup qualifies. Avg evidence ${evAvg}.`
: `Loaded the ${scanned} most active markets and analyzed ${analyzed}. Showing ${filtered.length} of ${pool.length} ${focus} ideas; agents can buy only independently confirmed setups that also fit their strategy and risk limits.`; : `Loaded the ${scanned} most active markets and analyzed ${analyzed}. Showing ${filtered.length} of ${pool.length} ${focus} ideas; agents can buy only independently confirmed setups that also fit their strategy and risk limits.`;
if(!filtered.length){root.innerHTML=`<div class="empty" style="grid-column:1/-1">No ${esc(focus)} suggestions in this live batch.</div>`;return;} if(!filtered.length){root.innerHTML=`<div class="empty" style="grid-column:1/-1">No ${esc(focus)} suggestions in this live batch.</div>`;return;}
root.innerHTML=filtered.map(s=>{ root.innerHTML=filtered.map(s=>{
@@ -4295,7 +4321,7 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
staleCacheIsMarkOnly:true,strategyEvidenceSurvivesBuilds:true,signalEvaluationHours:SIGNAL_EVAL_HOURS,signalRetryHours:SIGNAL_LEDGER_RETRY_HOURS, staleCacheIsMarkOnly:true,strategyEvidenceSurvivesBuilds:true,signalEvaluationHours:SIGNAL_EVAL_HOURS,signalRetryHours:SIGNAL_LEDGER_RETRY_HOURS,
signalDueFetchLimit:SIGNAL_LEDGER_DUE_FETCH_LIMIT,survivorshipSafeSignalGrading:true, signalDueFetchLimit:SIGNAL_LEDGER_DUE_FETCH_LIMIT,survivorshipSafeSignalGrading:true,
signalRoundTripCostCents:SIGNAL_ROUND_TRIP_COST*100,independentConfidence:true,uncertaintyGatedCalibration:true, signalRoundTripCostCents:SIGNAL_ROUND_TRIP_COST*100,independentConfidence:true,uncertaintyGatedCalibration:true,
historicalPrior:"reversal disabled; short-dated NO requires recent promotion; exact ranges excluded; sports trends blocked outside exploration; crypto, longshots, and YES sized down"}), historicalPrior:"Sports and Crypto trends observation-only; reversal and short-dated NO require recent cohort promotion; exact ranges excluded; longshots and YES sized down"}),
}); });
function runEngineSelfTest(){ function runEngineSelfTest(){
const market=(overrides={})=>Object.assign({ const market=(overrides={})=>Object.assign({
@@ -4306,6 +4332,8 @@ function runEngineSelfTest(){
},overrides); },overrides);
const news={"self-test":{source_count:3,latest_title:"Election polling update"}}; const news={"self-test":{source_count:3,latest_title:"Election polling update"}};
const trend=analyzeMarket(market(),news); const trend=analyzeMarket(market(),news);
const sportsTrend=analyzeMarket(market({category:"Sports",question:"Will the season champion win?"}),news);
const cryptoTrend=analyzeMarket(market({category:"Crypto",question:"Will the crypto proposal pass?"}),news);
const noSignal=analyzeMarket(market({price_change_1h:0,price_change_1d:0.002,price_change_1w:-0.002}),news); const noSignal=analyzeMarket(market({price_change_1h:0,price_change_1d:0.002,price_change_1w:-0.002}),news);
const reversal=analyzeMarket(market({price_change_1h:-0.01,price_change_1d:0.07,price_change_1w:0.02}),news); const reversal=analyzeMarket(market({price_change_1h:-0.01,price_change_1d:0.07,price_change_1w:0.02}),news);
const intervalContract=analyzeMarket(market({id:"range-test",question:"Will Count Binface win 2030% of votes?"}),news); const intervalContract=analyzeMarket(market({id:"range-test",question:"Will Count Binface win 2030% of votes?"}),news);
@@ -4334,10 +4362,11 @@ function runEngineSelfTest(){
let priorMarketId="prior-reversal-0",priorIndex=0; let priorMarketId="prior-reversal-0",priorIndex=0;
while(stableExploration(AGENTS[0].id,priorMarketId))priorMarketId=`prior-reversal-${++priorIndex}`; while(stableExploration(AGENTS[0].id,priorMarketId))priorMarketId=`prior-reversal-${++priorIndex}`;
const priorReversal=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:priorMarketId,signal_type:"reversal",quality:"reversal",category:"Sports",side:"NO",entry_price:0.42}); const priorReversal=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:priorMarketId,signal_type:"reversal",quality:"reversal",category:"Sports",side:"NO",entry_price:0.42});
const priorCryptoLongshot=historicalOpportunityPrior({signal_type:"trend",category:"Crypto",entry_price:0.20}); const priorCryptoLongshot=historicalOpportunityPrior({signal_type:"catalyst",category:"Crypto",entry_price:0.20});
let sportsPriorId="prior-sports-0",sportsPriorIndex=0; let sportsPriorId="prior-sports-0",sportsPriorIndex=0;
while(stableExploration(AGENTS[0].id,sportsPriorId))sportsPriorId=`prior-sports-${++sportsPriorIndex}`; while(stableExploration(AGENTS[0].id,sportsPriorId))sportsPriorId=`prior-sports-${++sportsPriorIndex}`;
const priorSportsTrend=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:sportsPriorId,signal_type:"trend",quality:"confirmed",category:"Sports",side:"NO",entry_price:0.58}); const priorSportsTrend=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:sportsPriorId,signal_type:"trend",quality:"confirmed",category:"Sports",side:"NO",entry_price:0.58});
const priorCryptoTrend=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:"prior-crypto",signal_type:"trend",quality:"confirmed",category:"Crypto",side:"YES",entry_price:0.42});
const priorPoliticsTrend=historicalOpportunityPrior({signal_type:"trend",category:"Politics",side:"NO",entry_price:0.58}); const priorPoliticsTrend=historicalOpportunityPrior({signal_type:"trend",category:"Politics",side:"NO",entry_price:0.58});
const shortNoPrior=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:"short-no",signal_type:"trend",quality:"confirmed",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:14}); const shortNoPrior=learnedOpportunity(AGENTS[0],defaultPortfolio(),{market_id:"short-no",signal_type:"trend",quality:"confirmed",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:14});
const longNoPrior=historicalOpportunityPrior({signal_type:"trend",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:30}); const longNoPrior=historicalOpportunityPrior({signal_type:"trend",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:30});
@@ -4369,6 +4398,12 @@ function runEngineSelfTest(){
const noisyCalibration=calibratedOpportunity(calibrationCandidate,calibrationFromReturns(Array.from({length:24},(_,i)=>i%2?0.12:-0.12))); const noisyCalibration=calibratedOpportunity(calibrationCandidate,calibrationFromReturns(Array.from({length:24},(_,i)=>i%2?0.12:-0.12)));
const promotedShortNoCandidate={market_id:"short-no-promoted",signal_type:"trend",quality:"confirmed",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:14}; const promotedShortNoCandidate={market_id:"short-no-promoted",signal_type:"trend",quality:"confirmed",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:14};
const promotedShortNo=learnedOpportunity(AGENTS[0],defaultPortfolio(),promotedShortNoCandidate,null,calibrationFromReturns(Array(24).fill(0.12),promotedShortNoCandidate)); const promotedShortNo=learnedOpportunity(AGENTS[0],defaultPortfolio(),promotedShortNoCandidate,null,calibrationFromReturns(Array(24).fill(0.12),promotedShortNoCandidate));
const promotedReversalCandidate={market_id:"reversal-promoted",signal_type:"reversal",quality:"reversal",category:"Politics",side:"NO",entry_price:0.58,days_to_resolution:45};
const promotedReversalCalibration=calibrationFromReturns(Array(24).fill(0.12),promotedReversalCandidate);
const promotedReversal=learnedOpportunity(AGENTS[0],defaultPortfolio(),promotedReversalCandidate,null,promotedReversalCalibration);
const promotedReversalSuggestion=applyAdaptiveMarketPromotion(Object.assign({},reversal,{entry_candidate:true}),promotedReversalCalibration);
const blockedCryptoPromotion=applyAdaptiveMarketPromotion(Object.assign({},cryptoTrend,{entry_candidate:true}),calibrationFromReturns(Array(24).fill(0.12),{
market_id:"crypto-positive",signal_type:"trend",quality:"confirmed",category:"Crypto",side:"YES",entry_price:0.42,days_to_resolution:45}));
const legacyBuildCalibration=buildSignalCalibration({pending:[],outcomes:[Object.assign({},calibrationCandidate, const legacyBuildCalibration=buildSignalCalibration({pending:[],outcomes:[Object.assign({},calibrationCandidate,
{strategy_version:PREVIOUS_STRATEGY_VERSION,return:0.10,evaluated_at:closedAt})]}); {strategy_version:PREVIOUS_STRATEGY_VERSION,return:0.10,evaluated_at:closedAt})]});
const currentStrategyCalibration=buildSignalCalibration({pending:[],outcomes:[Object.assign({},calibrationCandidate, const currentStrategyCalibration=buildSignalCalibration({pending:[],outcomes:[Object.assign({},calibrationCandidate,
@@ -4427,11 +4462,11 @@ function runEngineSelfTest(){
delete executableBook.positions[0].price_status; delete executableBook.positions[0].price_status;
markToMarket(executableBook,{"offline-mark":market({id:"offline-mark",yes_price:0.8,no_price:0.2})},AGENTS[0],{policyExits:true,executeTrades:true}); markToMarket(executableBook,{"offline-mark":market({id:"offline-mark",yes_price:0.8,no_price:0.2})},AGENTS[0],{policyExits:true,executeTrades:true});
const buildMigrationState=defaultState(); const buildMigrationState=defaultState();
buildMigrationState.engine_version=46;buildMigrationState.strategy_version=SUGGESTION_ENGINE_VERSION; buildMigrationState.engine_version=47;buildMigrationState.strategy_version=SUGGESTION_ENGINE_VERSION;
buildMigrationState.agents.value.engine_baseline={version:SUGGESTION_ENGINE_VERSION,started_at:hoursAgo(2),equity:9876.54}; buildMigrationState.agents.value.engine_baseline={version:SUGGESTION_ENGINE_VERSION,started_at:hoursAgo(2),equity:9876.54};
reconcileStateVersions(buildMigrationState); reconcileStateVersions(buildMigrationState);
const strategyMigrationState=defaultState(); const strategyMigrationState=defaultState();
strategyMigrationState.engine_version=46;strategyMigrationState.strategy_version=PREVIOUS_STRATEGY_VERSION; strategyMigrationState.engine_version=47;strategyMigrationState.strategy_version=PREVIOUS_STRATEGY_VERSION;
strategyMigrationState.agents.value.cash=9876.54; strategyMigrationState.agents.value.cash=9876.54;
strategyMigrationState.agents.value.engine_baseline={version:PREVIOUS_STRATEGY_VERSION,started_at:hoursAgo(2),equity:10000}; strategyMigrationState.agents.value.engine_baseline={version:PREVIOUS_STRATEGY_VERSION,started_at:hoursAgo(2),equity:10000};
reconcileStateVersions(strategyMigrationState); reconcileStateVersions(strategyMigrationState);
@@ -4439,6 +4474,7 @@ function runEngineSelfTest(){
migrateAgentChatIdentity(chatMigrationFixture); migrateAgentChatIdentity(chatMigrationFixture);
return {buildVersion:BUILD_VERSION,version:SUGGESTION_ENGINE_VERSION, return {buildVersion:BUILD_VERSION,version:SUGGESTION_ENGINE_VERSION,
trend:{ready:trend.trade_ready,quality:trend.quality,side:trend.side,margin:trend.net_edge}, trend:{ready:trend.trade_ready,quality:trend.quality,side:trend.side,margin:trend.net_edge},
auditedTrendCategories:{sportsObservationOnly:!sportsTrend.trade_ready&&sportsTrend.signal_type==="trend",cryptoObservationOnly:!cryptoTrend.trade_ready&&cryptoTrend.signal_type==="trend"},
noSignal:{ready:noSignal.trade_ready,quality:noSignal.quality,signal:noSignal.signal_type,margin:noSignal.net_edge}, noSignal:{ready:noSignal.trade_ready,quality:noSignal.quality,signal:noSignal.signal_type,margin:noSignal.net_edge},
reversal:{ready:reversal.trade_ready,quality:reversal.quality,side:reversal.side,margin:reversal.net_edge}, reversal:{ready:reversal.trade_ready,quality:reversal.quality,side:reversal.side,margin:reversal.net_edge},
highEntryTargets:targets,targetsReachable:targets.every(x=>x>0.82&&x<1), highEntryTargets:targets,targetsReachable:targets.every(x=>x>0.82&&x<1),
@@ -4446,9 +4482,12 @@ function runEngineSelfTest(){
calibrationSamples:calibrationProfile.samples,trendMarketScore:learnedTrend.market_score,reversalMarketScore:learnedReversal.market_score, calibrationSamples:calibrationProfile.samples,trendMarketScore:learnedTrend.market_score,reversalMarketScore:learnedReversal.market_score,
historicalPriorBlocksReversal:!priorReversal.allowed&&priorReversal.blocked_by==="historical", historicalPriorBlocksReversal:!priorReversal.allowed&&priorReversal.blocked_by==="historical",
reversalRemainsBlockedForExplorer:!explorerReversal.allowed&&explorerReversal.blocked_by==="historical", reversalRemainsBlockedForExplorer:!explorerReversal.allowed&&explorerReversal.blocked_by==="historical",
recentProofCanUnlockReversal:promotedReversal.allowed&&promotedReversal.market_state==="promoted"&&promotedReversalSuggestion.trade_ready,
hardBlockedCryptoCannotSelfPromote:!blockedCryptoPromotion.trade_ready,
enduranceReplacesReversal:agentAcceptsSuggestion(AGENTS.find(a=>a.id==="reversal"),trend)&&!agentAcceptsSuggestion(AGENTS.find(a=>a.id==="reversal"),reversal), enduranceReplacesReversal:agentAcceptsSuggestion(AGENTS.find(a=>a.id==="reversal"),trend)&&!agentAcceptsSuggestion(AGENTS.find(a=>a.id==="reversal"),reversal),
historicalPriorSizesRisk:!priorCryptoLongshot.blocked&&priorCryptoLongshot.multiplier<1&&priorCryptoLongshot.features.length===2, historicalPriorSizesRisk:!priorCryptoLongshot.blocked&&priorCryptoLongshot.multiplier<1&&priorCryptoLongshot.features.length===2,
historicalPriorBlocksSportsTrend:!priorSportsTrend.allowed&&priorSportsTrend.blocked_by==="historical", historicalPriorBlocksSportsTrend:!priorSportsTrend.allowed&&priorSportsTrend.blocked_by==="historical",
historicalPriorBlocksCryptoTrend:!priorCryptoTrend.allowed&&priorCryptoTrend.blocked_by==="historical",
historicalPriorAllowsPoliticsTrend:!priorPoliticsTrend.blocked, historicalPriorAllowsPoliticsTrend:!priorPoliticsTrend.blocked,
blocksShortDatedNo:!shortNoPrior.allowed&&shortNoPrior.blocked_by==="historical", blocksShortDatedNo:!shortNoPrior.allowed&&shortNoPrior.blocked_by==="historical",
recentProofCanUnlockShortDatedNo:promotedShortNo.allowed&&promotedShortNo.market_state==="promoted", recentProofCanUnlockShortDatedNo:promotedShortNo.allowed&&promotedShortNo.market_state==="promoted",
+1
View File
@@ -1,5 +1,6 @@
{ {
"scripts": { "scripts": {
"evaluate:neg-risk": "node scripts/evaluate-neg-risk.mjs",
"evaluate:signals": "node scripts/evaluate-signals.mjs", "evaluate:signals": "node scripts/evaluate-signals.mjs",
"evaluate:settlements": "node scripts/evaluate-settlements.mjs" "evaluate:settlements": "node scripts/evaluate-settlements.mjs"
}, },
+84
View File
@@ -0,0 +1,84 @@
const GAMMA = "https://gamma-api.polymarket.com";
const EVENT_LIMIT = Math.max(20, Math.min(1000, Number(process.env.NEG_RISK_EVENTS || 300)));
const COST_CENTS = Math.max(0, Math.min(5, Number(process.env.NEG_RISK_COST_CENTS || 0.5)));
const MIN_LIQUIDITY = Math.max(0, Number(process.env.NEG_RISK_MIN_LIQUIDITY || 1000));
async function fetchJson(url, attempts = 3) {
let lastError;
for (let attempt = 0; attempt < attempts; attempt++) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(20000), headers: { accept: "application/json" } });
if (response.ok) return response.json();
lastError = new Error(`${response.status} ${response.statusText}`);
if (response.status !== 429 && response.status < 500) break;
} catch (error) { lastError = error; }
await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
}
throw lastError || new Error("request failed");
}
function parseJson(value) {
if (Array.isArray(value)) return value;
try { return JSON.parse(value || "[]"); } catch { return []; }
}
function number(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
async function fetchEvents(limit) {
const events = [], pageSize = 100;
for (let offset = 0; offset < limit; offset += pageSize) {
const size = Math.min(pageSize, limit - offset);
const params = new URLSearchParams({ active: "true", closed: "false", archived: "false",
limit: String(size), offset: String(offset), order: "volume24hr", ascending: "false" });
const page = await fetchJson(`${GAMMA}/events?${params}`);
if (!Array.isArray(page) || !page.length) break;
events.push(...page);
if (page.length < size) break;
}
return events.slice(0, limit);
}
function evaluateEvent(event) {
if (!event?.negRisk || event.enableNegRisk === false) return null;
const allMarkets = Array.isArray(event.markets) ? event.markets : [];
if (allMarkets.length < 2 || allMarkets.some((market) => market.closed || market.active === false || market.acceptingOrders === false)) return null;
const legs = allMarkets.map((market) => {
const prices = parseJson(market.outcomePrices).map(number);
return { id: String(market.id || ""), question: market.question || "", yes: prices[0],
bid: number(market.bestBid), ask: number(market.bestAsk), liquidity: number(market.liquidityNum || market.liquidity) || 0 };
});
if (legs.some((leg) => !leg.id || leg.yes == null || leg.bid == null || leg.ask == null
|| leg.bid < 0 || leg.ask > 1 || leg.ask < leg.bid || leg.liquidity < MIN_LIQUIDITY)) return null;
const count = legs.length, costPerLeg = COST_CENTS / 100;
const yesCost = legs.reduce((sum, leg) => sum + leg.ask, 0);
const yesProfit = 1 - yesCost - count * costPerLeg;
const noCost = count - legs.reduce((sum, leg) => sum + leg.bid, 0);
const noProfit = count - 1 - noCost - count * costPerLeg;
const yesReturn = yesCost > 0 ? yesProfit / yesCost : 0;
const noReturn = noCost > 0 ? noProfit / noCost : 0;
const side = yesReturn >= noReturn ? "YES_BUNDLE" : "NO_BUNDLE";
return { eventId: String(event.id || ""), title: event.title || "", slug: event.slug || "", markets: count,
minimumLiquidity: Math.min(...legs.map((leg) => leg.liquidity)), side,
executableCost: side === "YES_BUNDLE" ? yesCost : noCost,
worstCasePayout: side === "YES_BUNDLE" ? 1 : count - 1,
netProfitPerBundle: side === "YES_BUNDLE" ? yesProfit : noProfit,
netReturn: side === "YES_BUNDLE" ? yesReturn : noReturn,
theoreticalYesSum: legs.reduce((sum, leg) => sum + leg.yes, 0), legs };
}
const events = await fetchEvents(EVENT_LIMIT);
const evaluated = events.map(evaluateEvent).filter(Boolean).sort((a, b) => b.netReturn - a.netReturn);
const actionable = evaluated.filter((event) => event.netProfitPerBundle > 0);
const compact = (event) => ({ eventId: event.eventId, title: event.title, markets: event.markets, side: event.side,
executableCost: +event.executableCost.toFixed(4), worstCasePayout: event.worstCasePayout,
netProfitPerBundle: +event.netProfitPerBundle.toFixed(4), netReturn: +event.netReturn.toFixed(4),
minimumLiquidity: +event.minimumLiquidity.toFixed(2), theoreticalYesSum: +event.theoreticalYesSum.toFixed(4),
url: event.slug ? `https://polymarket.com/event/${event.slug}` : "" });
console.log(JSON.stringify({ generatedAt: new Date().toISOString(), requestedEvents: EVENT_LIMIT,
fetchedEvents: events.length, eligibleNegativeRiskEvents: evaluated.length, actionableBundles: actionable.length,
estimatedCostCentsPerLeg: COST_CENTS, minimumLiquidityPerLeg: MIN_LIQUIDITY,
actionable: actionable.slice(0, 50).map(compact), bestObserved: evaluated.slice(0, 20).map(compact) }, null, 2));
+21 -4
View File
@@ -179,7 +179,16 @@ const RULES = [
{ name: "follow_reversal", field: "netReturn", test: (row) => row.type === "reversal" }, { name: "follow_reversal", field: "netReturn", test: (row) => row.type === "reversal" },
{ name: "fade_trend", field: "fadeNetReturn", test: (row) => row.type === "trend" }, { name: "fade_trend", field: "fadeNetReturn", test: (row) => row.type === "trend" },
{ name: "fade_trend_yes_move", field: "fadeNetReturn", test: (row) => row.type === "trend" && row.side === "YES" }, { name: "fade_trend_yes_move", field: "fadeNetReturn", test: (row) => row.type === "trend" && row.side === "YES" },
{ name: "fade_trend_no_move", field: "fadeNetReturn", test: (row) => row.type === "trend" && row.side === "NO" },
{ name: "fade_trend_mid", field: "fadeNetReturn", test: (row) => row.type === "trend" && row.band === "mid" },
{ name: "fade_trend_favorites", field: "fadeNetReturn", test: (row) => row.type === "trend" && ["favorite", "heavy-favorite"].includes(row.band) },
{ name: "fade_trend_longshots", field: "fadeNetReturn", test: (row) => row.type === "trend" && row.band === "longshot" },
{ name: "fade_strong_trend", field: "fadeNetReturn", test: (row) => row.type === "trend" && Math.abs(row.dayMove) >= 0.015 && Math.abs(row.weekMove) >= 0.03 }, { name: "fade_strong_trend", field: "fadeNetReturn", test: (row) => row.type === "trend" && Math.abs(row.dayMove) >= 0.015 && Math.abs(row.weekMove) >= 0.03 },
{ name: "fade_moderate_trend", field: "fadeNetReturn", test: (row) => row.type === "trend" && Math.abs(row.dayMove) <= 0.03 && Math.abs(row.weekMove) <= 0.10 },
...["Politics", "Sports", "Crypto", "Economy", "Pop Culture", "Other"].map((category) => ({
name: `fade_trend_${category.toLowerCase().replace(/\s+/g, "_")}`, field: "fadeNetReturn",
test: (row) => row.type === "trend" && row.category === category,
})),
]; ];
function evaluateRules(rows) { function evaluateRules(rows) {
@@ -196,17 +205,25 @@ function chronologicalEvaluation(rows) {
ordered.filter((row) => row.observedAt >= cut1 && row.observedAt < cut2), ordered.filter((row) => row.observedAt >= cut1 && row.observedAt < cut2),
ordered.filter((row) => row.observedAt >= cut2)]; ordered.filter((row) => row.observedAt >= cut2)];
const thirdRules = thirds.map(evaluateRules), pooled = evaluateRules(ordered); const thirdRules = thirds.map(evaluateRules), pooled = evaluateRules(ordered);
const trainRules = evaluateRules(train), testRules = evaluateRules(test);
const robustRules = Object.fromEntries(RULES.map((rule) => { const robustRules = Object.fromEntries(RULES.map((rule) => {
const segments = thirdRules.map((result) => result[rule.name]); const segments = thirdRules.map((result) => result[rule.name]);
const trainStats = trainRules[rule.name], testStats = testRules[rule.name], pooledStats = pooled[rule.name];
const enoughData = segments.every((segment) => segment.count >= 20 && segment.markets >= 5); const enoughData = segments.every((segment) => segment.count >= 20 && segment.markets >= 5);
const allPositive = enoughData && segments.every((segment) => segment.mean > 0 && segment.marketMean > 0); const trainTestPositive = trainStats.count >= 40 && testStats.count >= 20
const allNegative = enoughData && segments.every((segment) => segment.mean < 0 && segment.marketMean < 0); && trainStats.mean > 0 && trainStats.marketMean > 0 && testStats.mean > 0 && testStats.marketMean > 0;
const trainTestNegative = trainStats.count >= 40 && testStats.count >= 20
&& trainStats.mean < 0 && trainStats.marketMean < 0 && testStats.mean < 0 && testStats.marketMean < 0;
const allPositive = enoughData && trainTestPositive && pooledStats.lower90 > 0
&& segments.every((segment) => segment.mean > 0 && segment.marketMean > 0);
const allNegative = enoughData && trainTestNegative && pooledStats.upper90 < 0
&& segments.every((segment) => segment.mean < 0 && segment.marketMean < 0);
return [rule.name, { enoughData, allPositive, allNegative, return [rule.name, { enoughData, allPositive, allNegative,
minimumSegmentMean: Math.min(...segments.map((segment) => segment.mean)), minimumSegmentMean: Math.min(...segments.map((segment) => segment.mean)),
maximumSegmentMean: Math.max(...segments.map((segment) => segment.mean)), pooled: pooled[rule.name] }]; maximumSegmentMean: Math.max(...segments.map((segment) => segment.mean)), pooled: pooledStats }];
})); }));
return { splitTime: splitTime ? new Date(splitTime * 1000).toISOString() : null, return { splitTime: splitTime ? new Date(splitTime * 1000).toISOString() : null,
trainCount: train.length, testCount: test.length, train: evaluateRules(train), test: evaluateRules(test), trainCount: train.length, testCount: test.length, train: trainRules, test: testRules,
thirds: thirdRules, robustRules }; thirds: thirdRules, robustRules };
} }
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = "polymarket-arena-build-47"; const CACHE_NAME = "polymarket-arena-build-48";
const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"]; const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"];
self.addEventListener("install", event => { self.addEventListener("install", event => {