Enable selective buys for agents

This commit is contained in:
Theodore Song
2026-07-27 15:37:55 -04:00
parent 67beab0144
commit 82a1e0c831
+22 -9
View File
@@ -79,6 +79,8 @@ body:not(.personal-mode) [data-tab="live"]{display:none!important}
padding:12px 14px;background:rgba(251,191,36,.08);color:#f8e7b0;font-size:13px}
.personal-mode .personal-banner{display:block}
.personal-banner b{color:#fff}
.live-build-banner{display:block;margin:0 0 14px;padding:11px 14px;border-radius:14px;border:1px solid rgba(124,140,255,.30);background:linear-gradient(135deg,rgba(124,140,255,.14),rgba(56,210,230,.08));color:#dbeafe;font-size:12.5px}
.live-build-banner b{color:#fff}
/* ---------- hero ---------- */
.hero{position:relative;border:1px solid var(--border);border-radius:22px;padding:34px 32px;margin:14px 0 22px;
@@ -338,7 +340,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
<nav class="topnav">
<div class="brand">
<div class="logo">🏆</div>
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · strategies, copycats &amp; whales</div><div class="build-badge">Top 5,000 analysis · v17</div></div>
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · strategies, copycats &amp; whales</div><div class="build-badge">Top 5,000 analysis · v18</div></div>
</div>
<div class="tabs" id="tabs">
<button class="tab" data-tab="overview">Overview</button>
@@ -360,6 +362,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
<div class="personal-banner" id="personalBanner">
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
</div>
<div class="live-build-banner"><b>Build v18 active:</b> agents rank the live Polymarket universe, analyze the top 5,000 by activity/liquidity/gap, and use a selective BUY fallback so core agents do not sit entirely in cash.</div>
<!-- ============ OVERVIEW ============ -->
<section class="tabpanel" data-tab="overview">
@@ -740,7 +743,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
</section>
<footer>
Build top-5000-analysis-v17 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
Build top-5000-analysis-v18 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
</footer>
</div>
@@ -767,7 +770,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 = 17;
const SUGGESTION_ENGINE_VERSION = 18;
const FOCUS_KEY = "pma_focus_v1";
const VIEW_KEY = "pma_view_v1";
const PF_SORT_KEY = "pma_portfolio_sort_v1";
@@ -992,6 +995,7 @@ const MIN_SCOUT_VOLUME=1500;
const MIN_SCOUT_LIQUIDITY=150;
const MIN_SCOUT_EDGE=0.006;
const MIN_ENTRY_EDGE=0.032;
const MIN_SELECTIVE_ENTRY_EDGE=0.020;
const MIN_ENTRY_DAYS=3.0;
const EDGE_SCALE=0.13;
const MAX_STRATEGY_POSITIONS=16;
@@ -1077,6 +1081,12 @@ function selectMarketsForAnalysis(markets,limit=MARKET_ANALYSIS_LIMIT){
.slice(0,limit);
}
function timingSignal(d){if(d==null)return 0.4;if(d<1)return 0.1;if(d<=3)return 0.5;if(d<=90)return 1.0-(d-3)/87.0*0.4;return 0.35;}
function effectiveEntryEdge(s){
const net=Math.abs(Number(s&&s.net_edge));
const raw=Math.abs(Number(s&&s.edge));
if(Number.isFinite(net)&&net>0)return Math.max(net,raw*0.72);
return Number.isFinite(raw)?raw:0;
}
function analyzeMarket(m,realWorldSignals={}){
if(m.volume<MIN_SCOUT_VOLUME||m.liquidity<MIN_SCOUT_LIQUIDITY)return null;
const p=m.yes_price,fair=fairValue(m),edgeYes=fair-p,edge=Math.abs(edgeYes),policy=categoryPolicy(m.category);
@@ -1087,9 +1097,12 @@ function analyzeMarket(m,realWorldSignals={}){
const mis=Math.min(1,absNet/EDGE_SCALE);
const conviction=+((W.LIQ*liq+W.MOM*mom+W.MIS*mis+W.TIME*timing)*82+evidence.score*18).toFixed(1);
const minEdge=Math.max(MIN_ENTRY_EDGE,policy.minEdge);
const tradeReady=absNet>=minEdge&&m.volume>=Math.max(MIN_VOLUME,policy.minVol)&&m.liquidity>=Math.max(MIN_LIQUIDITY,policy.minLiq)&&evidence.score>=policy.minEvidence;
const strictTradeReady=absNet>=minEdge&&m.volume>=Math.max(MIN_VOLUME,policy.minVol)&&m.liquidity>=Math.max(MIN_LIQUIDITY,policy.minLiq)&&evidence.score>=policy.minEvidence;
const selectiveTradeReady=edge>=MIN_SELECTIVE_ENTRY_EDGE&&conviction>=58&&m.volume>=MIN_VOLUME&&m.liquidity>=MIN_LIQUIDITY&&evidence.score>=0.42&&m.yes_price>=0.08&&m.yes_price<=0.92;
const tradeReady=strictTradeReady||selectiveTradeReady;
let side=edgeYes>=0?"YES":"NO",entry=side==="YES"?p:m.no_price,rationale;
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.`;}
if(selectiveTradeReady&&!strictTradeReady){rationale=`Selective BUY from top-5,000 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.`;}
else{rationale=`Watch only: raw gap ${(edge*100).toFixed(1)}c becomes ${(absNet*100).toFixed(1)}c after friction/chase/category penalties, so agents need stronger evidence before buying.`;}
const drivers=[policy.label];
@@ -1103,7 +1116,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),
quality:tradeReady?"EV+":"watch",
quality:strictTradeReady?"EV+":(tradeReady?"selective":"watch"),
side,entry_price:+entry.toFixed(4),conviction,volume:m.volume,volume_24hr:m.volume_24hr,liquidity:m.liquidity,
trade_ready:tradeReady,
days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers,rationale};
@@ -1681,9 +1694,9 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
const avoid=avoidMarketIds||new Set();
const cands=rankedSugs.map(s=>peerAdjustedSuggestion(s,peerStats)).filter(s=>s.trade_ready&&(s.side==="YES"||s.side==="NO")&&s.peer_conviction>=d.minConv
&&s.conviction>=48&&s.entry_price>=0.08&&s.entry_price<=0.92
&&Math.abs(s.net_edge!=null?s.net_edge:s.edge)>=MIN_ENTRY_EDGE&&(s.days_to_resolution==null||s.days_to_resolution>=MIN_ENTRY_DAYS)
&&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.50)
&&(s.evidence_score==null||s.evidence_score>=0.40||s.conviction>=62)
&&!avoid.has(String(s.market_id))&&!hasPosition(p,s.market_id)&&!hasStoppedToday(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,openedIds=[];
@@ -1694,7 +1707,7 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
if(investable<=50)break;
let frac;
if(cfg.flat){frac=d.maxFrac;}
else{const base=(s.peer_conviction/100)*Math.min(1,Math.abs(s.net_edge!=null?s.net_edge:s.edge)/EDGE_SCALE);frac=Math.min(d.maxFrac,cfg.kelly*base);}
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);