diff --git a/index.html b/index.html
index 6fa714f..9d5cf14 100644
--- a/index.html
+++ b/index.html
@@ -362,7 +362,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
Personal research mode. 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.
- Build v32 active: five quality-first agents compete with five experimental aggressive agents. The audit preserves valid positions, and each core agent may add one small evidence-heavy probe per cycle without being blocked by an aggressive-only crowd. Aggressive agents can lose more; no return is guaranteed.
+ Build v33 active: every agent now has a persistent exposure band and a quality-checked allocation lane that rebuilds positions when cash rises above its target. Refills still require positive post-cost edge, evidence, liquidity, and settlement time. Aggressive agents can lose more; no return is guaranteed.
@@ -771,7 +771,7 @@ const EXIT_STALE_DAYS = 10;
const EXIT_RESOLUTION_DAYS = 2;
const AGENTS_KEY = "pma_agents_v2";
const SUG_KEY = "pma_suggestions_v5";
-const SUGGESTION_ENGINE_VERSION = 32;
+const SUGGESTION_ENGINE_VERSION = 33;
const FOCUS_KEY = "pma_focus_v1";
const VIEW_KEY = "pma_view_v1";
const PF_SORT_KEY = "pma_portfolio_sort_v1";
@@ -852,43 +852,43 @@ const AGENTS = [
{id:"value", name:"Value Hunter", emoji:"🎯", color:"#7c8cff", kind:"strategy",
blurb:"Looks for the widest net gap between model fair value and current market price after liquidity friction, chase penalties, and real-world evidence checks. It prefers trades where the crowd appears too pessimistic or too optimistic, then sizes positions moderately so one bad read does not dominate the portfolio.",
rank:(s)=>[...s].sort((a,b)=>(Math.abs(b.net_edge||b.edge||0)*120+b.conviction+Number(b.evidence_score||0)*18)-(Math.abs(a.net_edge||a.edge||0)*120+a.conviction+Number(a.evidence_score||0)*18)),
- maxNew:4, maxFrac:0.055, minConv:58, kelly:0.24},
+ maxNew:4, maxFrac:0.055, minConv:58, kelly:0.24, targetExposure:0.62, drawdownExposure:0.42},
{id:"momentum", name:"Momentum Chaser", emoji:"🚀", color:"#34d399", kind:"strategy",
blurb:"Chases markets where attention and price are accelerating together. It prioritizes true positive-edge entries, then uses tightly sized trend probes when one-day and one-week price direction agree, spreads are narrow, and enough time remains before settlement.",
rank:(s)=>[...s].sort((a,b)=>(b.volume_24hr*(b.conviction+20)*Math.max(.4,Math.abs(b.net_edge||b.edge||0)*18))-(a.volume_24hr*(a.conviction+20)*Math.max(.4,Math.abs(a.net_edge||a.edge||0)*18))),
- maxNew:5, maxFrac:0.06, minConv:57, kelly:0.30},
+ maxNew:5, maxFrac:0.06, minConv:57, kelly:0.30, targetExposure:0.72, drawdownExposure:0.50},
{id:"favorite", name:"Favorite Backer", emoji:"🛡️", color:"#38d2e6", kind:"strategy",
blurb:"A lower-drama agent that only considers outcomes already priced as favorites. It tries to grind out steadier returns by backing high-probability markets only when net edge and evidence quality survive the stricter checks.",
rank:(s)=>[...s].filter(x=>x.entry_price>=0.6).sort((a,b)=>(b.entry_price+Math.abs(b.net_edge||b.edge||0)+Number(b.evidence_score||0)*.2)-(a.entry_price+Math.abs(a.net_edge||a.edge||0)+Number(a.evidence_score||0)*.2)||b.conviction-a.conviction),
- maxNew:4, maxFrac:0.05, minConv:58, kelly:0.24},
+ maxNew:4, maxFrac:0.05, minConv:58, kelly:0.24, targetExposure:0.58, drawdownExposure:0.38},
{id:"longshot", name:"Longshot Hunter", emoji:"🎰", color:"#fbbf24", kind:"strategy",
blurb:"The swing-for-upside strategy. It hunts cheaper contracts that the model thinks are being ignored, but now discounts thin tails unless evidence and net edge are strong enough to justify the volatility.",
- rank:(s)=>[...s].filter(x=>x.entry_price<=0.4).sort((a,b)=>(Math.abs(b.net_edge||b.edge||0)*100+b.conviction+Number(b.evidence_score||0)*22-b.entry_price*12)-(Math.abs(a.net_edge||a.edge||0)*100+a.conviction+Number(a.evidence_score||0)*22-a.entry_price*12)),
- maxNew:4, maxFrac:0.035, minConv:60, kelly:0.16},
+ rank:(s)=>[...s].filter(x=>x.entry_price<=0.48).sort((a,b)=>(Math.abs(b.net_edge||b.edge||0)*100+b.conviction+Number(b.evidence_score||0)*22-b.entry_price*12)-(Math.abs(a.net_edge||a.edge||0)*100+a.conviction+Number(a.evidence_score||0)*22-a.entry_price*12)),
+ maxNew:6, maxFrac:0.07, minConv:57, kelly:0.25, allocationFrac:0.065, targetExposure:0.50, drawdownExposure:0.38},
{id:"diversifier", name:"The Diversifier", emoji:"🌐", color:"#c77dff", kind:"strategy",
blurb:"Builds a broad basket instead of making a few concentrated bets. It spreads smaller, flatter positions across high-conviction, evidence-backed ideas while avoiding crowded overlap and weak net-edge fillers.",
rank:(s)=>[...s].sort((a,b)=>(b.conviction+Number(b.evidence_score||0)*15+Math.abs(b.net_edge||b.edge||0)*55)-(a.conviction+Number(a.evidence_score||0)*15+Math.abs(a.net_edge||a.edge||0)*55)),
- maxNew:6, maxFrac:0.03, minConv:56, kelly:0.15, flat:true},
+ maxNew:6, maxFrac:0.03, minConv:56, kelly:0.15, flat:true, targetExposure:0.70, drawdownExposure:0.48},
{id:"catalyst", name:"Catalyst Strike", emoji:"⚡", color:"#ff8a4c", kind:"strategy", aggressive:true,
blurb:"An aggressive event-driven strategy that concentrates on markets with fresh outside context, multiple evidence signals, and enough activity for a fast repricing. It is willing to enter earlier than the core agents when a measurable catalyst and positive post-cost edge agree, while accepting larger swings to capture the move.",
rank:(s)=>[...s].sort((a,b)=>(Number(b.evidence_source_count||0)*28+Number(b.evidence_score||0)*32+effectiveEntryEdge(b)*170+b.conviction+Math.log10(Number(b.volume_24hr||0)+1)*5)-(Number(a.evidence_source_count||0)*28+Number(a.evidence_score||0)*32+effectiveEntryEdge(a)*170+a.conviction+Math.log10(Number(a.volume_24hr||0)+1)*5)),
- maxNew:7, maxFrac:0.09, minConv:52, kelly:0.46, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8},
+ maxNew:7, maxFrac:0.09, minConv:52, kelly:0.46, allocationFrac:0.055, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8, targetExposure:0.72, drawdownExposure:0.60},
{id:"reversal", name:"Reversal Edge", emoji:"↻", color:"#ff5f91", kind:"strategy", aggressive:true,
blurb:"A contrarian mean-reversion strategy that looks for sharp one-day moves the fair-value model believes went too far. It buys against the recent direction only when the remaining post-cost gap, evidence, and liquidity support a snapback, then uses larger positions than the core group to make successful reversals matter.",
- rank:(s)=>[...s].sort((a,b)=>(Math.abs(Number(b.price_change_1d||0))*95+effectiveEntryEdge(b)*190+Number(b.evidence_score||0)*24+b.conviction)-(Math.abs(Number(a.price_change_1d||0))*95+effectiveEntryEdge(a)*190+Number(a.evidence_score||0)*24+a.conviction)),
- maxNew:6, maxFrac:0.08, minConv:54, kelly:0.42, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8},
+ rank:(s)=>[...s].map(reversalCandidate).filter(Boolean).sort((a,b)=>(Math.abs(Number(b.price_change_1d||0))*95+effectiveEntryEdge(b)*190+Number(b.evidence_score||0)*24+b.conviction)-(Math.abs(Number(a.price_change_1d||0))*95+effectiveEntryEdge(a)*190+Number(a.evidence_score||0)*24+a.conviction)),
+ maxNew:6, maxFrac:0.08, minConv:54, kelly:0.42, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8, targetExposure:0.80, drawdownExposure:0.65},
{id:"breakout", name:"Breakout Velocity", emoji:"▲", color:"#00d5b5", kind:"strategy", aggressive:true,
blurb:"A high-tempo breakout strategy that follows confirmed price direction when momentum, fresh volume, tight spreads, and positive modeled edge line up. It builds positions quickly and keeps less idle cash so strong moves can drive the portfolio, knowing failed breakouts will create sharper drawdowns.",
rank:(s)=>[...s].sort((a,b)=>(Number(b.momentum_strength||0)*65+Math.log10(Number(b.volume_24hr||0)+1)*9+effectiveEntryEdge(b)*130+b.conviction)-(Number(a.momentum_strength||0)*65+Math.log10(Number(a.volume_24hr||0)+1)*9+effectiveEntryEdge(a)*130+a.conviction)),
- maxNew:8, maxFrac:0.075, minConv:50, kelly:0.44, maxPositions:22, maxCategoryPct:0.40, maxCycleDrawdown:8},
+ maxNew:8, maxFrac:0.075, minConv:50, kelly:0.44, maxPositions:22, maxCategoryPct:0.40, maxCycleDrawdown:8, targetExposure:0.88, drawdownExposure:0.72},
{id:"tailalpha", name:"Tail Risk Alpha", emoji:"✦", color:"#ffd166", kind:"strategy", aggressive:true,
blurb:"An asymmetric-upside strategy focused on cheaper contracts whose implied odds look too low relative to evidence and model value. It accepts a low hit rate and volatile results in exchange for larger potential payoffs, but still requires liquidity, time to resolution, and a positive post-cost edge before entering.",
- rank:(s)=>[...s].filter(x=>Number(x.entry_price||1)<=0.38).sort((a,b)=>(effectiveEntryEdge(b)*220+Number(b.evidence_score||0)*42+b.conviction-Number(b.entry_price||0)*35)-(effectiveEntryEdge(a)*220+Number(a.evidence_score||0)*42+a.conviction-Number(a.entry_price||0)*35)),
- maxNew:6, maxFrac:0.06, minConv:55, kelly:0.34, maxPositions:18, maxCategoryPct:0.35, maxCycleDrawdown:8},
+ rank:(s)=>[...s].filter(x=>Number(x.entry_price||1)<=0.42).sort((a,b)=>(effectiveEntryEdge(b)*220+Number(b.evidence_score||0)*42+b.conviction-Number(b.entry_price||0)*35)-(effectiveEntryEdge(a)*220+Number(a.evidence_score||0)*42+a.conviction-Number(a.entry_price||0)*35)),
+ maxNew:6, maxFrac:0.07, minConv:54, kelly:0.38, allocationFrac:0.065, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8, targetExposure:0.72, drawdownExposure:0.58},
{id:"conviction", name:"Conviction Max", emoji:"◆", color:"#b58cff", kind:"strategy", aggressive:true,
blurb:"A concentrated highest-conviction strategy that ignores mediocre opportunities and commits heavily to the few markets with the strongest combined edge, evidence, and confidence. It deliberately runs a smaller book with position sizes up to ten percent of equity, producing the clearest risk-and-reward behavior of the aggressive group.",
rank:(s)=>[...s].sort((a,b)=>(effectiveEntryEdge(b)*250+Number(b.evidence_score||0)*55+b.conviction*1.4+Number(b.evidence_source_count||0)*12)-(effectiveEntryEdge(a)*250+Number(a.evidence_score||0)*55+a.conviction*1.4+Number(a.evidence_source_count||0)*12)),
- maxNew:4, maxFrac:0.10, minConv:60, kelly:0.58, maxPositions:12, maxCategoryPct:0.45, maxCycleDrawdown:8},
+ maxNew:4, maxFrac:0.10, minConv:60, kelly:0.58, maxPositions:12, maxCategoryPct:0.45, maxCycleDrawdown:8, targetExposure:0.82, drawdownExposure:0.68},
];
const agentById = (id) => AGENTS.find(a=>a.id===id) || AGENTS[0];
@@ -1014,11 +1014,14 @@ const MIN_TREND_EDGE=0.012;
const MIN_LIQUIDITY_EDGE=0.018;
const MIN_CORE_PROBE_EDGE=0.015;
const MIN_AGGRESSIVE_EDGE=0.008;
+const MIN_ALLOCATION_EDGE=0.006;
const MIN_ENTRY_DAYS=7.0;
const EDGE_SCALE=0.13;
const MAX_STRATEGY_POSITIONS=30;
const MAX_NEW_POSITION_PCT=0.06;
const MAX_PROBE_POSITION_PCT=0.015;
+const MAX_CORE_ALLOCATION_PCT=0.04;
+const MAX_AGGRESSIVE_ALLOCATION_PCT=0.045;
const MAX_CATEGORY_EXPOSURE_PCT=0.25;
const MAX_CYCLE_DRAWDOWN_PCT=4;
const STOP_COOLDOWN_HOURS=72;
@@ -1081,12 +1084,16 @@ async function fetchRealWorldSignals(markets){
return d.signals||{};
}catch(e){return {};}
}
-function fairValue(m){const p=m.yes_price,mom=momentumSignal(m),liq=liquiditySignal(m);
+function fairValue(m){
+ const p=m.yes_price,mom=momentumSignal(m),liq=liquiditySignal(m),trend=directionalPriceSignal(m);
if(p>0.92)return Math.min(0.995,p+0.025*liq*(1-p)*4);
if(p<0.08)return Math.max(0.005,p-0.12*p);
- const directional=mom>0.62?(mom-0.62)*0.08:(mom<0.26?(mom-0.26)*0.035:0);
- const liquidBump=(liq-0.55)*0.012;
- return clamp(p+directional+liquidBump,0.03,0.97);}
+ const direction=trend.side==="YES"?1:(trend.side==="NO"?-1:0);
+ const directional=trend.aligned?direction*(0.018+trend.strength*0.050)*(0.72+0.28*liq):0;
+ const activityConfirmation=trend.aligned?direction*clamp((mom-0.45)*0.018,-0.004,0.010):0;
+ const liquidAnchor=(0.5-p)*0.008*liq;
+ return clamp(p+directional+activityConfirmation+liquidAnchor,0.03,0.97);
+}
function quickMarketGap(m){
if(!m||!Number.isFinite(m.yes_price))return 0;
return Math.abs(fairValue(m)-m.yes_price);
@@ -1130,6 +1137,21 @@ function effectiveEntryEdge(s){
const net=Math.abs(Number(s&&s.net_edge));
return Number.isFinite(net)?net:0;
}
+function reversalCandidate(s){
+ const day=Number(s&&s.price_change_1d||0),magnitude=Math.abs(day);
+ if(!s||magnitude<0.025||magnitude>0.16||s.jump_risk)return null;
+ const side=day>0?"NO":"YES",entry=side==="YES"?Number(s.yes_price):Number(s.no_price);
+ const policy=categoryPolicy(s.category),gross=Math.min(0.075,magnitude*0.48);
+ const net=Math.max(0,gross-Number(s.friction||0)-policy.uncertainty);
+ const evidence=Number(s.evidence_score||0),days=Number(s.days_to_resolution);
+ const ready=net>=MIN_AGGRESSIVE_EDGE&&entry>=0.08&&entry<=0.92&&evidence>=0.46
+ &&Number(s.spread)>0&&Number(s.spread)<=0.035&&Number(s.volume)>=MIN_VOLUME&&Number(s.volume_24hr)>=500
+ &&Number(s.liquidity)>=MIN_LIQUIDITY&&Number.isFinite(days)&&days>=MIN_ENTRY_DAYS;
+ const quality=ready?(net>=MIN_SELECTIVE_ENTRY_EDGE?"selective":"allocation"):"watch";
+ return Object.assign({},s,{side,entry_price:+entry.toFixed(4),net_edge:+((side==="YES"?1:-1)*net).toFixed(4),quality,trade_ready:ready,
+ conviction:+Math.max(Number(s.conviction||0),Math.min(82,50+magnitude*220+evidence*8)).toFixed(1),
+ rationale:ready?`Mean-reversion setup: ${side} opposes a ${(magnitude*100).toFixed(1)}% one-day move and retains a ${(net*100).toFixed(1)}c post-cost snapback edge with usable evidence and liquidity.`:`Watch only: the one-day move has not left enough post-cost mean-reversion edge.`});
+}
function analyzeMarket(m,realWorldSignals={}){
if(m.volume0&&m.spread<=0.03&&m.volume_24hr>=750&&m.liquidity>=1500
&&m.days_to_resolution!=null&&m.days_to_resolution>=10&&p>=0.10&&p<=0.90
&&Math.abs(m.price_change_1d||0)<=0.12&&(trend.strength>=0.50||evidence.source_count>=1);
+ const allocationTradeReady=!strictTradeReady&&!selectiveTradeReady&&!trendTradeReady&&!liquidityTradeReady&&!coreProbeTradeReady&&!speculativeTradeReady&&!jumpRisk
+ &&absNet>=MIN_ALLOCATION_EDGE&&conviction>=50&&evidence.score>=0.46
+ &&m.spread>0&&m.spread<=0.035&&m.volume>=MIN_VOLUME&&m.volume_24hr>=500&&m.liquidity>=MIN_LIQUIDITY
+ &&m.days_to_resolution!=null&&m.days_to_resolution>=MIN_ENTRY_DAYS&&p>=0.08&&p<=0.92
+ &&Math.abs(m.price_change_1d||0)<=0.12;
const explorationTradeReady=false;
- const tradeReady=(strictTradeReady||selectiveTradeReady||trendTradeReady||liquidityTradeReady||coreProbeTradeReady||speculativeTradeReady||explorationTradeReady)&&!jumpRisk;
- const tradeConviction=trendTradeReady?Math.max(conviction,55+trend.strength*35):(liquidityTradeReady?Math.max(conviction,70):(coreProbeTradeReady?Math.max(conviction,62):(speculativeTradeReady?Math.max(conviction,55):(explorationTradeReady?Math.max(conviction,60):conviction))));
+ const tradeReady=(strictTradeReady||selectiveTradeReady||trendTradeReady||liquidityTradeReady||coreProbeTradeReady||speculativeTradeReady||allocationTradeReady||explorationTradeReady)&&!jumpRisk;
+ const tradeConviction=trendTradeReady?Math.max(conviction,55+trend.strength*35):(liquidityTradeReady?Math.max(conviction,70):(coreProbeTradeReady?Math.max(conviction,62):(speculativeTradeReady?Math.max(conviction,55):(allocationTradeReady?Math.max(conviction,50):(explorationTradeReady?Math.max(conviction,60):conviction)))));
let side=trendTradeReady?trend.side:(edgeYes>=0?"YES":"NO");
let entry=side==="YES"?p:m.no_price,rationale;
if(trendTradeReady){rationale=`Confirmed trend trade: ${side} has ${Math.round(trend.strength*100)} trend strength, a ${(absNet*100).toFixed(1)}c post-cost edge, ${pct(m.spread)} spread, strong live activity, and enough time before resolution.`;}
else if(liquidityTradeReady){rationale=`Liquidity-confirmed trade: ${side} retains a ${(absNet*100).toFixed(1)}c post-cost edge in a high-conviction, tight-spread market with enough time before resolution.`;}
else if(coreProbeTradeReady){rationale=`Evidence-heavy core probe: ${side} retains a ${(absNet*100).toFixed(1)}c post-cost edge with ${Math.round(evidence.score*100)} evidence, deep liquidity, strong live volume, and at least two weeks before resolution. Core agents may enter only at probe size.`;}
else if(speculativeTradeReady){rationale=`Aggressive setup: ${side} retains a measured ${(absNet*100).toFixed(1)}c post-cost edge with ${Math.round(evidence.score*100)} evidence, adequate liquidity, and a live catalyst or directional signal. Only aggressive agents may take this higher-risk entry.`;}
+ else if(allocationTradeReady){rationale=`Allocation candidate: ${side} retains a positive ${(absNet*100).toFixed(1)}c post-cost edge with ${Math.round(evidence.score*100)} evidence, usable liquidity, and at least one week before resolution. Agents below their exposure floor may use it at reduced size.`;}
else if(selectiveTradeReady&&!strictTradeReady){rationale=`Selective BUY from top-500 active scan: raw gap ${(edge*100).toFixed(1)}c, conviction ${Math.round(conviction)}, high activity/liquidity, and evidence ${Math.round(evidence.score*100)}. Net edge is conservative, so sizing stays disciplined.`;}
else if(tradeReady&&edgeYes>0){rationale=`Trade-ready after costs: fair value ${pct(fair)} vs market ${pct(p)} leaves ${(absNet*100).toFixed(1)}c net edge for YES after liquidity, chase, and ${m.category} evidence checks.`;}
else if(tradeReady&&edgeYes<0){rationale=`Trade-ready after costs: fair value ${pct(fair)} vs market ${pct(p)} makes YES look overpriced; NO has ${(absNet*100).toFixed(1)}c net edge after penalties.`;}
@@ -1187,7 +1215,7 @@ function analyzeMarket(m,realWorldSignals={}){
clob_yes:(m.clob_token_ids||[])[0]||null,clob_no:(m.clob_token_ids||[])[1]||null,
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),evidence_source_count:evidence.source_count||0,
- quality:strictTradeReady?"EV+":(selectiveTradeReady?"selective":(trendTradeReady?"trend":(liquidityTradeReady?"liquidity":(coreProbeTradeReady?"core-probe":(speculativeTradeReady?"speculative":(explorationTradeReady?"explore":"watch")))))),
+ quality:strictTradeReady?"EV+":(selectiveTradeReady?"selective":(trendTradeReady?"trend":(liquidityTradeReady?"liquidity":(coreProbeTradeReady?"core-probe":(speculativeTradeReady?"speculative":(allocationTradeReady?"allocation":(explorationTradeReady?"explore":"watch"))))))),
side,entry_price:+entry.toFixed(4),conviction:+tradeConviction.toFixed(1),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,
momentum_strength:+trend.strength.toFixed(2),trade_ready:tradeReady,jump_risk:jumpRisk,
@@ -1489,17 +1517,22 @@ function emotionalState(ret,trail,trend,rank){
}
function adaptiveDecision(cfg,p,rank,total,leaderEq){
const eq=equity(p),ret=(eq/p.starting_balance-1)*100,trail=((leaderEq||eq)-eq)/p.starting_balance*100;
+ const positionValue=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
+ const currentExposure=eq>0?positionValue/eq:0;
const trend=recentReturnDelta(p);
const emo=emotionalState(ret,trail,trend,rank);
const aggressive=!!cfg.aggressive;
let minConv=cfg.minConv??0,maxNew=cfg.maxNew??6,maxFrac=cfg.maxFrac??0.06,reserve=aggressive?0.02:0.08;
+ let targetExposure=cfg.targetExposure??(aggressive?0.80:0.62);
let mode=aggressive?"Aggressive Hunt":"Quality First";
let reason=aggressive?"keeping cash near 2% and pressing qualified edge with a larger risk budget.":"deploying capital only when a strategy-specific setup clears its edge and evidence floors.";
if(rank===1){
mode="Compound the Lead";minConv+=aggressive?0:1;maxFrac*=aggressive?1.02:0.97;reserve=aggressive?0.03:0.10;
+ targetExposure=Math.max(aggressive?0.72:0.52,targetExposure-0.04);
reason=aggressive?"leading the race and continuing to press its strongest qualifying setups while its wider risk cap remains active.":"leading the race, so it compounds only through setups that preserve the quality of its existing book.";
}else if(trail>6){
mode=aggressive?"Aggressive Catch-Up":"Disciplined Catch-Up";minConv+=aggressive?0:2;maxNew+=aggressive?1:0;maxFrac*=aggressive?1.05:0.95;reserve=aggressive?0.02:0.10;
+ if(aggressive)targetExposure=Math.min(0.90,targetExposure+0.02);
reason=aggressive?"behind the leader, so it can add one more qualified entry and deploy at its full risk budget without weakening the edge floor.":"behind the leader, but it will recover through better expected value rather than increasing low-quality turnover.";
}else if(trend>1.5){
mode="Momentum Press";minConv-=1;maxNew+=1;maxFrac*=1.08;reserve=0.02;
@@ -1507,11 +1540,13 @@ function adaptiveDecision(cfg,p,rank,total,leaderEq){
}
if(ret<-5){
mode=aggressive?"Risk-On Recovery":"Selective Recovery";minConv+=aggressive?1:4;maxNew=Math.min(maxNew,aggressive?5:3);maxFrac*=aggressive?0.90:0.80;reserve=aggressive?0.05:0.15;
+ targetExposure=Math.max(aggressive?0.60:0.38,targetExposure-(aggressive?0.05:0.08));
reason=aggressive?"in drawdown, but it remains invested through its best qualifying signals with modestly smaller sizing.":"in drawdown, so it raises its evidence threshold and reduces new-trade size until the signal quality recovers.";
}
if(ret<-18){
- mode=aggressive?"Aggressive Drawdown Control":"Drawdown Repair";minConv+=aggressive?3:8;maxNew=Math.min(maxNew,aggressive?3:2);maxFrac=Math.min(maxFrac,aggressive?0.06:0.025);reserve=aggressive?0.12:0.25;
- reason=aggressive?"down more than 18%, so it keeps hunting with three high-quality recovery slots while reducing maximum size to 6%.":"down more than 18%, so it stops forcing volume and admits only two unusually strong recovery setups per cycle.";
+ mode=aggressive?"Aggressive Drawdown Control":"Drawdown Repair";minConv+=aggressive?3:6;maxNew=Math.min(maxNew,aggressive?4:4);maxFrac=Math.min(maxFrac,aggressive?0.06:0.04);reserve=aggressive?0.12:0.18;
+ targetExposure=cfg.drawdownExposure??(aggressive?0.65:0.42);
+ reason=aggressive?"down more than 18%, so it rebuilds toward a reduced exposure target through four positive-edge recovery slots while capping position size.":"down more than 18%, so it rebuilds a diversified recovery book toward a reduced exposure target instead of becoming trapped in cash.";
}
if(emo.mood==="impatient"||emo.mood==="frustrated"){
if(aggressive)maxNew+=1;else minConv+=1;
@@ -1520,7 +1555,7 @@ function adaptiveDecision(cfg,p,rank,total,leaderEq){
maxNew+=1;maxFrac*=1.05;
reason+=` Emotion layer: confidence lets it press winners a little harder.`;
}else if(emo.mood==="alarmed"){
- maxNew=Math.min(maxNew,aggressive?3:2);maxFrac=Math.min(maxFrac,aggressive?0.06:0.025);reserve=Math.max(reserve,aggressive?0.12:0.25);
+ maxNew=Math.min(maxNew,4);maxFrac=Math.min(maxFrac,aggressive?0.06:0.04);reserve=Math.max(reserve,aggressive?0.12:0.18);
reason+=` Emotion layer: alarm cannot trigger revenge trading; only qualifying setups remain eligible.`;
}
const operatingReserve=aggressive?0.025:0.06;
@@ -1530,11 +1565,16 @@ function adaptiveDecision(cfg,p,rank,total,leaderEq){
const cycleDrawdown=cycleStart>0?(eq/cycleStart-1)*100:0;
const drawdownLimit=cfg.maxCycleDrawdown||MAX_CYCLE_DRAWDOWN_PCT;
if(cycleDrawdown<=-drawdownLimit){
- mode="Daily Risk Pause";maxNew=0;reserve=1;
+ mode="Daily Risk Pause";maxNew=0;reserve=1;targetExposure=currentExposure;
reason=`down ${Math.abs(cycleDrawdown).toFixed(1)}% since today's first snapshot, so it will not add risk until the next trading day.`;
}
const maxPositionPct=aggressive?0.10:MAX_NEW_POSITION_PCT;
- return {mode,reason,emotion:emo.mood,urgency:+emo.urgency.toFixed(2),minConv:Math.max(0,Math.round(minConv)),maxNew:Math.max(0,Math.round(maxNew)),maxFrac:+Math.min(maxPositionPct,Math.max(0.01,maxFrac)).toFixed(3),reserve};
+ targetExposure=clamp(targetExposure,0,1-reserve);
+ const minExposure=Math.max(0,Math.min(targetExposure,targetExposure-(aggressive?0.14:0.16)));
+ const belowFloor=currentExposure+0.0010)reason+=` Current exposure is ${Math.round(currentExposure*100)}%, below its ${Math.round(minExposure*100)}% floor, so this cycle must prioritize small qualifying allocation trades.`;
+ return {mode,reason,emotion:emo.mood,urgency:+emo.urgency.toFixed(2),minConv:Math.max(0,Math.round(minConv)),maxNew:Math.max(0,Math.round(maxNew)),maxFrac:+Math.min(maxPositionPct,Math.max(0.01,maxFrac)).toFixed(3),reserve,
+ currentExposure:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:+minExposure.toFixed(3),belowFloor};
}
const stopKey=(posOrId)=>typeof posOrId==="string"?posOrId:String(posOrId.asset||posOrId.market_id||"");
function stoppedAt(value){
@@ -1772,6 +1812,16 @@ function occupiedStrategyMarkets(st,selfId=null){
});
return ids;
}
+function strategyExecutionOrder(st){
+ return [...AGENTS].sort((a,b)=>{
+ const score=cfg=>{
+ const p=st.agents&&st.agents[cfg.id]||defaultPortfolio(),eq=equity(p);
+ const invested=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
+ return (eq>0?invested/eq:0)/Math.max(0.01,Number(cfg.targetExposure||0.62));
+ };
+ return score(a)-score(b)||AGENTS.indexOf(a)-AGENTS.indexOf(b);
+ });
+}
function overlapKeeperScore(pos,cfg){
const conviction=Number(pos.conviction||cfg.minConv||50);
const pnlBoost=Math.max(-8,Math.min(8,Number(pos.unrealized_pnl||0)/75));
@@ -1790,9 +1840,9 @@ function reduceStrategyOverlap(st){
});
Object.values(groups).filter(g=>g.length>1).forEach(group=>{
group.sort((a,b)=>(b.score-a.score)||((b.pos.value||0)-(a.pos.value||0)));
- const probeOnly=group.every(({pos})=>["trend","liquidity","core-probe","speculative","explore"].includes(pos.quality));
+ const probeOnly=group.every(({pos})=>["trend","liquidity","core-probe","speculative","allocation","explore"].includes(pos.quality));
const coreProbe=group.some(({pos})=>pos.quality==="core-probe");
- group.slice(coreProbe?4:(probeOnly?3:1)).forEach(({p,pos})=>closePosition(p,pos,"Overlap guard rotated this agent into a different market","EXIT"));
+ group.slice(coreProbe?4:(probeOnly?3:2)).forEach(({p,pos})=>closePosition(p,pos,"Overlap guard rotated this agent into a different market","EXIT"));
});
AGENTS.filter(a=>a.kind==="strategy").forEach(cfg=>{
const p=st.agents&&st.agents[cfg.id]; if(p)p.positions=(p.positions||[]).filter(pos=>!pos.closed_at);
@@ -1802,74 +1852,105 @@ function agentAcceptsSuggestion(cfg,s){
const quality=s.quality||"watch",edge=effectiveEntryEdge(s),evidence=Number(s.evidence_score||0);
if(quality==="explore"||quality==="watch")return false;
if(cfg.id==="value")return (["EV+","selective"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE)
- ||(quality==="core-probe"&&edge>=0.017&&evidence>=0.68);
+ ||(quality==="core-probe"&&edge>=0.017&&evidence>=0.68)
+ ||(quality==="allocation"&&edge>=0.010&&evidence>=0.46);
if(cfg.id==="momentum")return ["EV+","selective"].includes(quality)
||(quality==="trend"&&edge>=0.015&&Number(s.momentum_strength||0)>=0.65&&evidence>=0.54)
- ||(quality==="core-probe"&&Number(s.momentum_strength||0)>=0.48&&evidence>=0.68);
+ ||(quality==="core-probe"&&Number(s.momentum_strength||0)>=0.48&&evidence>=0.68)
+ ||(quality==="allocation"&&edge>=0.008&&Number(s.momentum_strength||0)>=0.35&&evidence>=0.46);
if(cfg.id==="favorite")return Number(s.entry_price||0)>=0.62
- &&["EV+","selective","liquidity","core-probe"].includes(quality)&&edge>=MIN_CORE_PROBE_EDGE;
- if(cfg.id==="longshot")return Number(s.entry_price||0)<=0.40
- &&(["EV+","selective"].includes(quality)||(quality==="trend"&&edge>=0.025&&evidence>=0.62)||(quality==="core-probe"&&edge>=0.017&&evidence>=0.72));
+ &&((["EV+","selective","liquidity","core-probe"].includes(quality)&&edge>=MIN_CORE_PROBE_EDGE)
+ ||(quality==="allocation"&&edge>=0.008&&evidence>=0.46));
+ if(cfg.id==="longshot")return Number(s.entry_price||0)<=0.48
+ &&(["EV+","selective"].includes(quality)||(quality==="trend"&&edge>=0.025&&evidence>=0.62)||(quality==="core-probe"&&edge>=0.017&&evidence>=0.72)
+ ||(quality==="allocation"&&edge>=0.010&&evidence>=0.48));
if(cfg.id==="diversifier")return ["EV+","selective","liquidity"].includes(quality)&&edge>=MIN_LIQUIDITY_EDGE
||(quality==="trend"&&edge>=0.020&&evidence>=0.58)
- ||(quality==="core-probe"&&edge>=MIN_CORE_PROBE_EDGE&&evidence>=0.68);
- if(cfg.id==="catalyst")return ["EV+","selective","trend","liquidity","speculative"].includes(quality)
+ ||(quality==="core-probe"&&edge>=MIN_CORE_PROBE_EDGE&&evidence>=0.68)
+ ||(quality==="allocation"&&edge>=0.008&&evidence>=0.46);
+ if(cfg.id==="catalyst")return ["EV+","selective","trend","liquidity","speculative","allocation"].includes(quality)
&&edge>=MIN_AGGRESSIVE_EDGE&&(Number(s.evidence_source_count||0)>=1||evidence>=0.62);
if(cfg.id==="reversal"){
const day=Number(s.price_change_1d||0),reversesMove=(s.side==="YES"&&day<=-0.02)||(s.side==="NO"&&day>=0.02);
- return reversesMove&&["EV+","selective","speculative"].includes(quality)&&edge>=0.012&&evidence>=0.50;
+ return reversesMove&&["EV+","selective","speculative","allocation"].includes(quality)
+ &&edge>=(quality==="allocation"?0.008:0.012)&&evidence>=(quality==="allocation"?0.46:0.50);
}
if(cfg.id==="breakout"){
const day=Number(s.price_change_1d||0),followsMove=(s.side==="YES"&&day>0)||(s.side==="NO"&&day<0);
- return followsMove&&["EV+","selective","trend","speculative"].includes(quality)
- &&edge>=(quality==="speculative"?MIN_AGGRESSIVE_EDGE:MIN_TREND_EDGE)&&Number(s.momentum_strength||0)>=0.50&&evidence>=0.48;
+ return followsMove&&["EV+","selective","trend","speculative","allocation"].includes(quality)
+ &&edge>=(quality==="allocation"?MIN_ALLOCATION_EDGE:(quality==="speculative"?MIN_AGGRESSIVE_EDGE:MIN_TREND_EDGE))
+ &&Number(s.momentum_strength||0)>=(quality==="allocation"?0.35:0.50)&&evidence>=(quality==="allocation"?0.46:0.48);
}
- if(cfg.id==="tailalpha")return Number(s.entry_price||0)<=0.38
- &&["EV+","selective","trend","speculative"].includes(quality)&&edge>=0.012&&evidence>=0.55;
- if(cfg.id==="conviction")return ["EV+","selective","speculative"].includes(quality)
- &&edge>=0.015&&Number(s.conviction||0)>=60&&evidence>=0.52;
+ if(cfg.id==="tailalpha")return Number(s.entry_price||0)<=0.42
+ &&["EV+","selective","trend","speculative","allocation"].includes(quality)
+ &&edge>=(quality==="allocation"?0.008:0.012)&&evidence>=(quality==="allocation"?0.48:0.55);
+ if(cfg.id==="conviction")return ["EV+","selective","speculative","allocation"].includes(quality)
+ &&edge>=(quality==="allocation"?0.010:0.015)&&Number(s.conviction||0)>=(quality==="allocation"?56:60)&&evidence>=(quality==="allocation"?0.48:0.52);
return ["EV+","selective"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE;
}
function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=null){
- const d=decision||{minConv:cfg.minConv,maxNew:cfg.maxNew,maxFrac:cfg.maxFrac,reserve:0.05};
- p.lastDecision=decision||null;
+ const d=decision||{minConv:cfg.minConv,maxNew:cfg.maxNew,maxFrac:cfg.maxFrac,reserve:0.05,targetExposure:cfg.targetExposure??(cfg.aggressive?0.80:0.62),minExposure:(cfg.targetExposure??(cfg.aggressive?0.80:0.62))-(cfg.aggressive?0.14:0.16)};
const maxPositions=cfg.maxPositions||MAX_STRATEGY_POSITIONS;
const categoryCap=cfg.maxCategoryPct||MAX_CATEGORY_EXPOSURE_PCT;
- const probeQualities=["trend","liquidity","core-probe","speculative","explore"];
- if((p.positions||[]).length>=maxPositions||d.maxNew<=0)return [];
+ const probeQualities=["trend","liquidity","core-probe","speculative","allocation","explore"];
+ const eqBefore=equity(p);
+ const positionValueBefore=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
+ const currentExposure=eqBefore>0?positionValueBefore/eqBefore:0;
+ const targetExposure=clamp(Number(d.targetExposure??cfg.targetExposure??0.62),0,1);
+ const minExposure=clamp(Number(d.minExposure??Math.max(0,targetExposure-0.16)),0,targetExposure);
+ const belowFloor=currentExposure+0.001s.trade_ready).length;
+ const strategyCount=rankedSugs.filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)).length;
+ 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,
+ allocationStatus:d.maxNew<=0?"New entries are paused by the daily drawdown limit.":`The ${maxPositions}-position portfolio limit is full.`});
+ return [];
+ }
const avoid=avoidMarketIds||new Set();
const cands=rankedSugs.map(s=>peerAdjustedSuggestion(s,peerStats)).filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)&&(s.side==="YES"||s.side==="NO")
- &&(cfg.aggressive?s.conviction>=d.minConv:(s.quality==="core-probe"?s.conviction>=Math.min(d.minConv,60):s.peer_conviction>=d.minConv))
+ &&(s.quality==="allocation"&&needsRefill
+ ?(cfg.aggressive?s.conviction>=Math.min(d.minConv,50):s.peer_conviction>=Math.min(d.minConv,52))
+ :(cfg.aggressive?s.conviction>=d.minConv:(s.quality==="core-probe"?s.conviction>=Math.min(d.minConv,60):s.peer_conviction>=d.minConv)))
&&s.conviction>=48&&s.entry_price>=0.08&&s.entry_price<=0.92
&&(probeQualities.includes(s.quality)||effectiveEntryEdge(s)>=MIN_SELECTIVE_ENTRY_EDGE)&&(s.days_to_resolution==null||s.days_to_resolution>=MIN_ENTRY_DAYS)
&&s.volume>=MIN_VOLUME&&s.liquidity>=MIN_LIQUIDITY&&(s.volume_24hr>=500||s.conviction>=62)
&&(s.evidence_score==null||s.evidence_score>=0.40||s.conviction>=62)
&&(!avoid.has(String(s.market_id))||(s.quality==="core-probe"&&!cfg.aggressive
?Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<4&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).coreSame||0)<1
- :probeQualities.includes(s.quality)&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<3))
+ :probeQualities.includes(s.quality)
+ ?Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<3
+ :belowFloor&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<2&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).opposite||0)===0))
&&!hasPosition(p,s.market_id)&&!hasRecentStop(p,s.market_id)&&(focus==="All"||!focus||s.category===focus))
.sort((a,b)=>(b.peer_conviction-a.peer_conviction)||((b.peer_boost||0)-(a.peer_boost||0)));
let opened=0,openedCoreProbes=0,openedIds=[];
+ let cycleBudgetRemaining=eqBefore*(cfg.aggressive?0.22:0.16);
for(const s of cands){
if(opened>=d.maxNew)break;
if((p.positions||[]).length>=maxPositions)break;
if(!cfg.aggressive&&s.quality==="core-probe"&&openedCoreProbes>=1)continue;
- const eq=equity(p),investable=p.cash-eq*d.reserve;
+ const eq=equity(p);
+ const positionValue=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
+ const exposureGap=Math.max(0,eq*targetExposure-positionValue);
+ const investable=Math.min(p.cash-eq*d.reserve,exposureGap,cycleBudgetRemaining);
if(investable<=50)break;
let frac;
- if(probeQualities.includes(s.quality)){frac=Math.min(d.maxFrac,cfg.aggressive?0.06:MAX_PROBE_POSITION_PCT);}
+ if(s.quality==="allocation"){frac=Math.min(d.maxFrac,cfg.allocationFrac??(cfg.aggressive?MAX_AGGRESSIVE_ALLOCATION_PCT:MAX_CORE_ALLOCATION_PCT));}
+ else if(probeQualities.includes(s.quality)){frac=Math.min(d.maxFrac,cfg.aggressive?0.06:MAX_PROBE_POSITION_PCT);}
else if(cfg.flat){frac=d.maxFrac;}
else{const base=(s.peer_conviction/100)*Math.min(1,effectiveEntryEdge(s)/EDGE_SCALE);frac=Math.min(d.maxFrac,cfg.kelly*base);}
if(s.peer_boost<0)frac*=0.82;
if(s.peer_boost>0&&decision&&decision.urgency>0.65)frac*=1.08;
let stake=Math.min(eq*frac,investable);
+ const recoveryCategoryCap=s.quality==="allocation"&&needsRefill?Math.max(categoryCap,cfg.aggressive?0.50:0.35):categoryCap;
const categoryValue=(p.positions||[]).filter(pos=>(pos.category||"Other")===(s.category||"Other")).reduce((sum,pos)=>sum+Number(pos.value||0),0);
- stake=Math.min(stake,Math.max(0,eq*categoryCap-categoryValue));
+ stake=Math.min(stake,Math.max(0,eq*recoveryCategoryCap-categoryValue));
if(stake<50)continue;
const entry=s.entry_price; if(entry<=0||entry>=1)continue;
const shares=+(stake/entry).toFixed(2),cost=+(shares*entry).toFixed(2);
if(cost>p.cash)continue;
p.cash=+(p.cash-cost).toFixed(2);
+ cycleBudgetRemaining=Math.max(0,cycleBudgetRemaining-cost);
p.positions.push({market_id:s.market_id,question:s.question,side:s.side,shares,
token_id:(s.side==="YES"?s.clob_yes:s.clob_no)||null,
entry_price:+entry.toFixed(4),current_price:+entry.toFixed(4),cost,value:cost,
@@ -1882,6 +1963,19 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
detail:`${decision?decision.mode+" mode — ":""}Bought ${shares} ${s.side} '${s.question.slice(0,40)}' @ ${pct(entry)} for ${fmtUSD(cost)} · net edge ${((Math.abs(s.net_edge!=null?s.net_edge:s.edge))*100).toFixed(1)}c · evidence ${Math.round((s.evidence_score||0)*100)}${s.peer_note?` (${s.peer_note})`:""}`});
opened++;if(!cfg.aggressive&&s.quality==="core-probe")openedCoreProbes++;openedIds.push(String(s.market_id));
}
+ const eqAfter=equity(p);
+ const positionValueAfter=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
+ const exposureAfter=eqAfter>0?positionValueAfter/eqAfter:0;
+ let allocationStatus;
+ if(opened)allocationStatus=`Refill active: opened ${opened} position${opened===1?"":"s"} and moved exposure from ${Math.round(currentExposure*100)}% to ${Math.round(exposureAfter*100)}% toward the ${Math.round(targetExposure*100)}% target.`;
+ else if(exposureAfter+0.001>=targetExposure)allocationStatus=`Exposure reached its ${Math.round(targetExposure*100)}% target.`;
+ else if(exposureAfter+0.001>=minExposure)allocationStatus=`Exposure is inside its ${Math.round(minExposure*100)}-${Math.round(targetExposure*100)}% operating band; qualified refills continue toward target.`;
+ else if(!tradeReadyCount)allocationStatus="No positive-edge markets cleared the portfolio safety checks this cycle.";
+ else if(!strategyCount)allocationStatus=`${tradeReadyCount} markets were trade-ready, but none fit this agent's strategy.`;
+ else if(!cands.length)allocationStatus=`${strategyCount} strategy matches were blocked by confidence, overlap, cooldown, or focus rules.`;
+ else allocationStatus="Qualified candidates existed, but position, 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:+minExposure.toFixed(3),belowFloor:exposureAfter+0.001priceMap[s.id]={yes_price:s.yes_price,no_price:s.no_price});
- for(const cfg of AGENTS.filter(a=>a.kind==="strategy")){
+ for(const cfg of AGENTS){
const p=st.agents[cfg.id];
markToMarket(p,priceMap,cfg,{policyExits:true});
}
reduceStrategyOverlap(st);
const claimedMarkets=new Set();
- for(const cfg of AGENTS.filter(a=>a.kind==="strategy")){
+ for(const cfg of strategyExecutionOrder(st)){
const p=st.agents[cfg.id];
const occupied=occupiedStrategyMarkets(st,cfg.id);
claimedMarkets.forEach(id=>occupied.add(id));
@@ -2013,7 +2107,7 @@ async function runDailyCycle(){
}
reduceStrategyOverlap(st);
const claimedMarkets=new Set();
- for(const cfg of AGENTS){
+ for(const cfg of strategyExecutionOrder(st)){
const p=st.agents[cfg.id];
const rank=preBoard.findIndex(x=>x.id===cfg.id)+1||preBoard.length;
const decision=adaptiveDecision(cfg,p,rank,preBoard.length,leaderEq);
@@ -2192,7 +2286,10 @@ function decisionSummary(p){
const d=p.lastDecision;
if(!d)return "No live adaptation yet; this agent is still using its base rulebook.";
const emotion=d.emotion?` Emotion: ${d.emotion}${d.urgency!=null?` (${Math.round(d.urgency*100)}% urgency)`:""}.`:"";
- 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}+`:""}.`;
+ const exposure=d.currentExposure!=null&&d.targetExposure!=null?` Exposure ${Math.round(d.currentExposure*100)}%; operating band ${Math.round(Number(d.minExposure||0)*100)}-${Math.round(d.targetExposure*100)}%.`:"";
+ 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.`:"";
+ 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}+`:""}.${exposure}${allocation}${candidates}`;
}
function renderAgentBrief(cfg,p,st){
const root=$("agentBrief"); if(!root)return;
@@ -2395,7 +2492,7 @@ function renderSuggestions(){
const scanned=Number(data.market_count||0).toLocaleString();
const analyzed=Number(data.analyzed_count||data.market_count||0).toLocaleString();
$("focusNote").textContent=focus==="All"
- ? `Loaded the ${scanned} most active markets, analyzed ${analyzed} by activity, liquidity, spread, and price direction, and kept ${all.length} ideas (${buyCount} BUY, ${watchCount} WATCH). Showing ${filtered.length}; BUY is tiered into strict EV, trend/liquidity probes, and sub-1% exploration trades. Avg evidence ${evAvg}.`
+ ? `Loaded the ${scanned} most active markets, analyzed ${analyzed} by activity, liquidity, spread, and price direction, and kept ${all.length} ideas (${buyCount} BUY, ${watchCount} WATCH). Showing ${filtered.length}; BUY includes strict EV setups, strategy probes, and reduced-size allocation candidates for agents below their exposure floor. Avg evidence ${evAvg}.`
: `Loaded the ${scanned} most active markets and analyzed ${analyzed}. Showing ${filtered.length} of ${pool.length} ${focus} ideas; agents may open strict EV entries, risk-capped probes, or tiny exploration trades while this is selected.`;
if(!filtered.length){root.innerHTML=`No ${esc(focus)} suggestions in this live batch.
`;return;}
root.innerHTML=filtered.map(s=>{
@@ -2607,7 +2704,7 @@ function renderAgentReports(){
const thesis=agentPlainBlurb(row.c,st);
const plan=agentCompetitionPlan(row.c,row,rank,leader);
const line=reportPositionLine(row.p);
- const crash=row.ret<-18?`Drawdown guard: ${row.c.aggressive?"This aggressive account remains active with up to three recovery entries, a 6% position ceiling, and a 12% cash reserve.":"This core account is limited to two unusually strong entries, a 2.5% position ceiling, and a 25% cash reserve."}
`:"";
+ const crash=row.ret<-18?`Drawdown recovery: This account is rebuilding toward a reduced ${Math.round(Number(row.p.lastDecision&&row.p.lastDecision.targetExposure||row.c.drawdownExposure||0)*100)}% exposure target through positive-edge, reduced-size entries. Stop-loss and category caps remain active.
`:"";
return `
${row.c.emoji} ${row.c.name}
#${rank} · ${ret}
Strategy: ${esc(thesis)}