mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-15 00:28:07 +00:00
Compare commits
1
Commits
0ef05ea734
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab333dd045 |
+286
-264
@@ -340,7 +340,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">Exposure engine · v33</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">Confirmation engine · v34</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>
|
||||||
@@ -362,7 +362,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 v33 active:</b> 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.</div>
|
<div class="live-build-banner"><b>Build v34 active:</b> exposure targets are ceilings, not quotas. New trades need a confirmed multi-window signal, liquidity, evidence, and a positive post-friction signal margin. Exits use holding hysteresis, reachable profit locks, and strict two-agent overlap. This remains paper trading; profits are not guaranteed.</div>
|
||||||
|
|
||||||
<!-- ============ OVERVIEW ============ -->
|
<!-- ============ OVERVIEW ============ -->
|
||||||
<section class="tabpanel" data-tab="overview">
|
<section class="tabpanel" data-tab="overview">
|
||||||
@@ -377,12 +377,12 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
<g fill="#d9b4ff"><circle cx="380" cy="40" r="6"/></g>
|
<g fill="#d9b4ff"><circle cx="380" cy="40" r="6"/></g>
|
||||||
</svg>
|
</svg>
|
||||||
<h1>Ten AI agents race to <span class="grad">beat the market</span></h1>
|
<h1>Ten AI agents race to <span class="grad">beat the market</span></h1>
|
||||||
<p>Each cycle loads the 500 most active Polymarket markets, ranks them by activity, liquidity, timing, and estimated price gap, then lets agents trade from that tighter pool.</p>
|
<p>Each cycle loads the 500 most active Polymarket markets, then requires direction to agree across independent time windows before liquidity, evidence, timing, and friction checks can make an idea trade-ready.</p>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<span class="hbadge">🏆 10 competing agents</span>
|
<span class="hbadge">🏆 10 competing agents</span>
|
||||||
<span class="hbadge">5 aggressive strategies</span>
|
<span class="hbadge">5 aggressive strategies</span>
|
||||||
<span class="hbadge">Top 500 active markets</span>
|
<span class="hbadge">Top 500 active markets</span>
|
||||||
<span class="hbadge">Ranked by activity + gap</span>
|
<span class="hbadge">Ranked by activity + confirmation</span>
|
||||||
<span class="hbadge">Live Polymarket data</span>
|
<span class="hbadge">Live Polymarket data</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -713,7 +713,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
<!-- ============ ABOUT ============ -->
|
<!-- ============ ABOUT ============ -->
|
||||||
<section class="tabpanel" data-tab="about">
|
<section class="tabpanel" data-tab="about">
|
||||||
<div class="section-title">ℹ️ The competition</div>
|
<div class="section-title">ℹ️ The competition</div>
|
||||||
<p class="muted" style="margin-top:-6px;max-width:820px">Every agent starts with <b>$10,000</b> in paper money. Five core strategies use disciplined entry rules, while five aggressive strategies pursue catalysts, reversals, breakouts, asymmetric tails, and concentrated conviction. The aggressive group accepts thinner but still positive measured edges, holds less cash, and risks more per position, so both gains and drawdowns can be substantially larger.</p>
|
<p class="muted" style="margin-top:-6px;max-width:820px">Every agent starts with <b>$10,000</b> in paper money. Five core strategies use disciplined entry rules, while five aggressive strategies pursue catalysts, reversals, breakouts, asymmetric tails, and concentrated conviction. Aggressive agents may size confirmed setups more heavily, but cash is retained whenever their strategy has no qualifying signal.</p>
|
||||||
<div class="about-grid" id="aboutAgents"></div>
|
<div class="about-grid" id="aboutAgents"></div>
|
||||||
<div class="card" style="margin-top:16px">
|
<div class="card" style="margin-top:16px">
|
||||||
<div class="card-h"><h3>The live cycle</h3></div>
|
<div class="card-h"><h3>The live cycle</h3></div>
|
||||||
@@ -744,7 +744,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
Build exposure-v33 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
|
Build confirmation-v34 · 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>
|
||||||
@@ -763,15 +763,16 @@ const STOP_LOSS_TIERS = [
|
|||||||
{id:"18",drawdown:0.18,sellFrac:1.00,label:"-18%"},
|
{id:"18",drawdown:0.18,sellFrac:1.00,label:"-18%"},
|
||||||
];
|
];
|
||||||
const GAIN_STOP_TIERS = [
|
const GAIN_STOP_TIERS = [
|
||||||
{id:"75",gain:0.75,multiple:1.75,sellFrac:0.25,label:"+75%"},
|
{id:"lock1",gain:0.15,maxGainFrac:0.35,sellFrac:0.25,label:"first profit lock"},
|
||||||
{id:"150",gain:1.50,multiple:2.50,sellFrac:0.25,label:"+150%"},
|
{id:"lock2",gain:0.30,maxGainFrac:0.60,sellFrac:0.25,label:"second profit lock"},
|
||||||
{id:"300",gain:3.00,multiple:4.00,sellFrac:0.25,label:"+300%"},
|
{id:"lock3",gain:0.50,maxGainFrac:0.85,sellFrac:0.25,label:"final profit lock"},
|
||||||
];
|
];
|
||||||
const EXIT_STALE_DAYS = 10;
|
const EXIT_STALE_DAYS = 21;
|
||||||
const EXIT_RESOLUTION_DAYS = 2;
|
const MIN_POLICY_HOLD_HOURS = 24;
|
||||||
|
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 SUGGESTION_ENGINE_VERSION = 33;
|
const SUGGESTION_ENGINE_VERSION = 34;
|
||||||
const FOCUS_KEY = "pma_focus_v1";
|
const FOCUS_KEY = "pma_focus_v1";
|
||||||
const VIEW_KEY = "pma_view_v1";
|
const VIEW_KEY = "pma_view_v1";
|
||||||
const PF_SORT_KEY = "pma_portfolio_sort_v1";
|
const PF_SORT_KEY = "pma_portfolio_sort_v1";
|
||||||
@@ -850,7 +851,7 @@ async function refreshTradeEmailStatus(){
|
|||||||
Five core and five aggressive strategies share the same live suggestion pool. */
|
Five core and five aggressive strategies share the same live suggestion pool. */
|
||||||
const AGENTS = [
|
const AGENTS = [
|
||||||
{id:"value", name:"Value Hunter", emoji:"🎯", color:"#7c8cff", kind:"strategy",
|
{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.",
|
blurb:"Looks for the strongest confirmed movement that remains after liquidity friction, chase penalties, and evidence checks. It requires agreement across independent time windows and 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)),
|
rank:(s)=>[...s].sort((a,b)=>(Math.abs(b.net_edge||b.edge||0)*120+b.conviction+Number(b.evidence_score||0)*18)-(Math.abs(a.net_edge||a.edge||0)*120+a.conviction+Number(a.evidence_score||0)*18)),
|
||||||
maxNew:4, maxFrac:0.055, minConv:58, kelly:0.24, targetExposure:0.62, drawdownExposure:0.42},
|
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",
|
{id:"momentum", name:"Momentum Chaser", emoji:"🚀", color:"#34d399", kind:"strategy",
|
||||||
@@ -862,7 +863,7 @@ const AGENTS = [
|
|||||||
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),
|
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, targetExposure:0.58, drawdownExposure:0.38},
|
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",
|
{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.",
|
blurb:"The swing-for-upside strategy. It considers cheaper contracts only when price direction is independently confirmed and the remaining signal margin, evidence, and liquidity justify the volatility.",
|
||||||
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)),
|
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},
|
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",
|
{id:"diversifier", name:"The Diversifier", emoji:"🌐", color:"#c77dff", kind:"strategy",
|
||||||
@@ -874,7 +875,7 @@ const AGENTS = [
|
|||||||
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)),
|
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, allocationFrac:0.055, maxPositions:18, maxCategoryPct:0.40, maxCycleDrawdown:8, targetExposure:0.72, drawdownExposure:0.60},
|
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,
|
{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.",
|
blurb:"A contrarian mean-reversion strategy that waits for an hourly reversal after a sharp one-day move. It buys against the earlier direction only when that snapback is confirmed by liquidity, evidence, and a positive post-friction signal margin.",
|
||||||
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)),
|
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},
|
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,
|
{id:"breakout", name:"Breakout Velocity", emoji:"▲", color:"#00d5b5", kind:"strategy", aggressive:true,
|
||||||
@@ -882,11 +883,11 @@ const AGENTS = [
|
|||||||
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)),
|
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, targetExposure:0.88, drawdownExposure:0.72},
|
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,
|
{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.",
|
blurb:"An asymmetric-upside strategy focused on cheaper contracts with confirmed direction and room to reprice. It accepts a low hit rate and volatile results in exchange for larger potential payoffs, but still requires evidence, liquidity, and time to resolution.",
|
||||||
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)),
|
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},
|
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,
|
{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.",
|
blurb:"A concentrated highest-conviction strategy that ignores mediocre opportunities and commits to the few markets with the strongest combined confirmation, evidence, and signal margin. It deliberately runs a smaller book so its best ideas matter without filling exposure for its own sake.",
|
||||||
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)),
|
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, targetExposure:0.82, drawdownExposure:0.68},
|
maxNew:4, maxFrac:0.10, minConv:60, kelly:0.58, maxPositions:12, maxCategoryPct:0.45, maxCycleDrawdown:8, targetExposure:0.82, drawdownExposure:0.68},
|
||||||
];
|
];
|
||||||
@@ -1002,21 +1003,21 @@ async function fetchMarketPrice(id){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Analysis engine ---------- */
|
/* ---------- Analysis engine ---------- */
|
||||||
const W={LIQ:0.28,MOM:0.22,MIS:0.34,TIME:0.16};
|
const W={LIQ:0.24,MOM:0.18,SIGNAL:0.38,TIME:0.20};
|
||||||
const MIN_VOLUME=18000;
|
const MIN_VOLUME=18000;
|
||||||
const MIN_LIQUIDITY=1400;
|
const MIN_LIQUIDITY=1400;
|
||||||
const MIN_SCOUT_VOLUME=1500;
|
const MIN_SCOUT_VOLUME=1500;
|
||||||
const MIN_SCOUT_LIQUIDITY=150;
|
const MIN_SCOUT_LIQUIDITY=150;
|
||||||
const MIN_SCOUT_EDGE=0.006;
|
const MIN_SCOUT_EDGE=0.006;
|
||||||
const MIN_ENTRY_EDGE=0.032;
|
const MIN_ENTRY_EDGE=0.028;
|
||||||
const MIN_SELECTIVE_ENTRY_EDGE=0.022;
|
const MIN_SELECTIVE_ENTRY_EDGE=0.022;
|
||||||
const MIN_TREND_EDGE=0.012;
|
const MIN_TREND_EDGE=0.018;
|
||||||
const MIN_LIQUIDITY_EDGE=0.018;
|
const MIN_LIQUIDITY_EDGE=0.020;
|
||||||
const MIN_CORE_PROBE_EDGE=0.015;
|
const MIN_CORE_PROBE_EDGE=0.024;
|
||||||
const MIN_AGGRESSIVE_EDGE=0.008;
|
const MIN_AGGRESSIVE_EDGE=0.016;
|
||||||
const MIN_ALLOCATION_EDGE=0.006;
|
const MIN_ALLOCATION_EDGE=MIN_AGGRESSIVE_EDGE;
|
||||||
const MIN_ENTRY_DAYS=7.0;
|
const MIN_ENTRY_DAYS=3.0;
|
||||||
const EDGE_SCALE=0.13;
|
const EDGE_SCALE=0.08;
|
||||||
const MAX_STRATEGY_POSITIONS=30;
|
const MAX_STRATEGY_POSITIONS=30;
|
||||||
const MAX_NEW_POSITION_PCT=0.06;
|
const MAX_NEW_POSITION_PCT=0.06;
|
||||||
const MAX_PROBE_POSITION_PCT=0.015;
|
const MAX_PROBE_POSITION_PCT=0.015;
|
||||||
@@ -1037,13 +1038,13 @@ function liquiditySignal(m){const vol=Math.min(1,Math.log10(m.volume+1)/6.0);con
|
|||||||
function momentumSignal(m){const da=m.volume_1wk?m.volume_1wk/7:0;if(da<=0)return m.volume_24hr>0?0.3:0;return clamp((m.volume_24hr/da-0.5)/2.0,0,1);}
|
function momentumSignal(m){const da=m.volume_1wk?m.volume_1wk/7:0;if(da<=0)return m.volume_24hr>0?0.3:0;return clamp((m.volume_24hr/da-0.5)/2.0,0,1);}
|
||||||
function categoryPolicy(cat){
|
function categoryPolicy(cat){
|
||||||
return ({
|
return ({
|
||||||
Politics:{minEdge:0.040,minVol:22000,minLiq:1800,minEvidence:0.48,uncertainty:0.006,label:"politics needs outside confirmation"},
|
Politics:{minEdge:0.022,minVol:22000,minLiq:1800,minEvidence:0.48,uncertainty:0.005,label:"politics needs confirmed price action and outside context"},
|
||||||
Sports:{minEdge:0.050,minVol:26000,minLiq:2200,minEvidence:0.55,uncertainty:0.010,label:"sports needs a large edge and low settlement-gap risk"},
|
Sports:{minEdge:0.024,minVol:26000,minLiq:2200,minEvidence:0.55,uncertainty:0.007,label:"sports needs strong confirmation and low settlement-gap risk"},
|
||||||
Crypto:{minEdge:0.045,minVol:28000,minLiq:2400,minEvidence:0.54,uncertainty:0.009,label:"crypto needs a larger edge after jump-risk penalties"},
|
Crypto:{minEdge:0.024,minVol:28000,minLiq:2400,minEvidence:0.54,uncertainty:0.007,label:"crypto needs confirmation after jump-risk penalties"},
|
||||||
Economy:{minEdge:0.044,minVol:22000,minLiq:1800,minEvidence:0.50,uncertainty:0.007,label:"macro markets need stronger evidence"},
|
Economy:{minEdge:0.023,minVol:22000,minLiq:1800,minEvidence:0.50,uncertainty:0.006,label:"macro markets need strong evidence"},
|
||||||
"Pop Culture":{minEdge:0.040,minVol:16000,minLiq:1300,minEvidence:0.46,uncertainty:0.005,label:"culture markets need attention confirmation"},
|
"Pop Culture":{minEdge:0.020,minVol:16000,minLiq:1300,minEvidence:0.46,uncertainty:0.004,label:"culture markets need attention confirmation"},
|
||||||
Other:{minEdge:0.048,minVol:24000,minLiq:1900,minEvidence:0.50,uncertainty:0.009,label:"uncategorized markets need the highest proof"},
|
Other:{minEdge:0.025,minVol:24000,minLiq:1900,minEvidence:0.52,uncertainty:0.007,label:"uncategorized markets need the highest proof"},
|
||||||
})[cat||"Other"]||({minEdge:0.048,minVol:24000,minLiq:1900,minEvidence:0.50,uncertainty:0.009,label:"uncategorized markets need the highest proof"});
|
})[cat||"Other"]||({minEdge:0.025,minVol:24000,minLiq:1900,minEvidence:0.52,uncertainty:0.007,label:"uncategorized markets need the highest proof"});
|
||||||
}
|
}
|
||||||
function frictionPenalty(m){
|
function frictionPenalty(m){
|
||||||
const liq=liquiditySignal(m);
|
const liq=liquiditySignal(m);
|
||||||
@@ -1054,13 +1055,13 @@ function frictionPenalty(m){
|
|||||||
return clamp(thinPenalty+tailPenalty+lowLiqPenalty,0.002,0.034);
|
return clamp(thinPenalty+tailPenalty+lowLiqPenalty,0.002,0.034);
|
||||||
}
|
}
|
||||||
function chasePenalty(m,rawEdge){
|
function chasePenalty(m,rawEdge){
|
||||||
const mom=momentumSignal(m),absEdge=Math.abs(rawEdge);
|
const day=Math.abs(Number(m.price_change_1d||0)),hour=Math.abs(Number(m.price_change_1h||0));
|
||||||
if(mom<0.72)return 0;
|
if(day<0.045&&hour<0.012)return 0;
|
||||||
return clamp((mom-0.72)*0.018+(absEdge<0.07?0.005:0),0,0.016);
|
return clamp(Math.max(0,day-0.045)*0.20+Math.max(0,hour-0.012)*0.35,0,0.024);
|
||||||
}
|
}
|
||||||
function textEvidenceSignal(m,external={}){
|
function textEvidenceSignal(m,external={}){
|
||||||
const text=`${m.question||""} ${(m.tags||[]).join(" ")}`.toLowerCase();
|
const text=`${m.question||""} ${(m.tags||[]).join(" ")}`.toLowerCase();
|
||||||
let evidence=0.42;
|
let evidence=0.30;
|
||||||
const drivers=[];
|
const drivers=[];
|
||||||
if(/\b(poll|approval|election|primary|senate|house|minister|president)\b/.test(text)){evidence+=0.08;drivers.push("political-event context");}
|
if(/\b(poll|approval|election|primary|senate|house|minister|president)\b/.test(text)){evidence+=0.08;drivers.push("political-event context");}
|
||||||
if(/\b(injury|starter|lineup|playoff|final|championship|tournament|wins?|score)\b/.test(text)){evidence+=0.09;drivers.push("sports-event context");}
|
if(/\b(injury|starter|lineup|playoff|final|championship|tournament|wins?|score)\b/.test(text)){evidence+=0.09;drivers.push("sports-event context");}
|
||||||
@@ -1068,7 +1069,7 @@ function textEvidenceSignal(m,external={}){
|
|||||||
if(/\b(release|box office|views|album|movie|stream|award)\b/.test(text)){evidence+=0.06;drivers.push("culture-attention context");}
|
if(/\b(release|box office|views|album|movie|stream|award)\b/.test(text)){evidence+=0.06;drivers.push("culture-attention context");}
|
||||||
if(m.days_to_resolution!=null&&m.days_to_resolution<=14){evidence+=0.07;drivers.push("near-term resolution");}
|
if(m.days_to_resolution!=null&&m.days_to_resolution<=14){evidence+=0.07;drivers.push("near-term resolution");}
|
||||||
if(m.volume_24hr>1500){evidence+=0.05;drivers.push("fresh market activity");}
|
if(m.volume_24hr>1500){evidence+=0.05;drivers.push("fresh market activity");}
|
||||||
if(external.source_count){evidence+=Math.min(0.16,0.04*external.source_count);drivers.push(`${external.source_count} recent news/context hit${external.source_count===1?"":"s"}`);}
|
if(external.source_count){evidence+=Math.min(0.20,0.05*external.source_count);drivers.push(`${external.source_count} recent context hit${external.source_count===1?"":"s"} (coverage only; direction comes from market confirmation)`);}
|
||||||
if(external.latest_title)drivers.push(`latest context: ${external.latest_title.slice(0,72)}${external.latest_title.length>72?"...":""}`);
|
if(external.latest_title)drivers.push(`latest context: ${external.latest_title.slice(0,72)}${external.latest_title.length>72?"...":""}`);
|
||||||
return {score:clamp(evidence,0,1),drivers,source_count:external.source_count||0,latest_title:external.latest_title||""};
|
return {score:clamp(evidence,0,1),drivers,source_count:external.source_count||0,latest_title:external.latest_title||""};
|
||||||
}
|
}
|
||||||
@@ -1084,15 +1085,30 @@ async function fetchRealWorldSignals(markets){
|
|||||||
return d.signals||{};
|
return d.signals||{};
|
||||||
}catch(e){return {};}
|
}catch(e){return {};}
|
||||||
}
|
}
|
||||||
function fairValue(m){
|
function confirmedMarketSignal(m,evidence={score:0,source_count:0}){
|
||||||
const p=m.yes_price,mom=momentumSignal(m),liq=liquiditySignal(m),trend=directionalPriceSignal(m);
|
const hour=Number(m.price_change_1h||0),day=Number(m.price_change_1d||0),week=Number(m.price_change_1w||0);
|
||||||
if(p>0.92)return Math.min(0.995,p+0.025*liq*(1-p)*4);
|
const daySign=Math.sign(day),weekSign=Math.sign(week),hourSign=Math.sign(hour);
|
||||||
if(p<0.08)return Math.max(0.005,p-0.12*p);
|
const trendAligned=daySign&&daySign===weekSign&&Math.abs(day)>=0.006&&Math.abs(week)>=0.012
|
||||||
const direction=trend.side==="YES"?1:(trend.side==="NO"?-1:0);
|
&&Math.abs(day)<=0.08&&Math.abs(week)<=0.18&&(!hourSign||hourSign===daySign||Math.abs(hour)<0.008);
|
||||||
const directional=trend.aligned?direction*(0.018+trend.strength*0.050)*(0.72+0.28*liq):0;
|
const reversalConfirmed=daySign&&Math.abs(day)>=0.04&&Math.abs(day)<=0.18&&hourSign===-daySign&&Math.abs(hour)>=0.004
|
||||||
const activityConfirmation=trend.aligned?direction*clamp((mom-0.45)*0.018,-0.004,0.010):0;
|
&&(!weekSign||weekSign!==daySign||Math.abs(week)<Math.abs(day)*1.6);
|
||||||
const liquidAnchor=(0.5-p)*0.008*liq;
|
if(!trendAligned&&!reversalConfirmed)return {side:null,type:"none",strength:0,gross_move:0,confidence:0,confirmed:false};
|
||||||
return clamp(p+directional+activityConfirmation+liquidAnchor,0.03,0.97);
|
const type=reversalConfirmed?"reversal":"trend";
|
||||||
|
const sign=reversalConfirmed?-daySign:daySign;
|
||||||
|
const liq=liquiditySignal(m),activity=momentumSignal(m);
|
||||||
|
const moveStrength=type==="trend"
|
||||||
|
?clamp(0.30+Math.abs(day)*4+Math.abs(week)*1.5+(daySign===weekSign?0.16:0),0,1)
|
||||||
|
:clamp(0.30+Math.abs(day)*3.2+Math.abs(hour)*8,0,1);
|
||||||
|
const confidence=clamp(moveStrength*0.48+liq*0.22+activity*0.12+Number(evidence.score||0)*0.18,0,1);
|
||||||
|
const side=sign>0?"YES":"NO",availableMove=side==="YES"?1-Number(m.yes_price||0):Number(m.yes_price||0);
|
||||||
|
const gross=Math.min(type==="trend"?0.018+moveStrength*0.052:0.016+moveStrength*0.045,Math.max(0,availableMove)*0.80);
|
||||||
|
return {side,type,strength:moveStrength,gross_move:gross,confidence,confirmed:confidence>=0.56};
|
||||||
|
}
|
||||||
|
function fairValue(m,external={}){
|
||||||
|
const evidence=textEvidenceSignal(m,external),signal=confirmedMarketSignal(m,evidence);
|
||||||
|
if(!signal.side)return m.yes_price;
|
||||||
|
const direction=signal.side==="YES"?1:-1;
|
||||||
|
return clamp(m.yes_price+direction*signal.gross_move,0.03,0.97);
|
||||||
}
|
}
|
||||||
function quickMarketGap(m){
|
function quickMarketGap(m){
|
||||||
if(!m||!Number.isFinite(m.yes_price))return 0;
|
if(!m||!Number.isFinite(m.yes_price))return 0;
|
||||||
@@ -1125,100 +1141,69 @@ function fastSettlementRisk(m){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function directionalPriceSignal(m){
|
function directionalPriceSignal(m){
|
||||||
const hour=Number(m.price_change_1h||0),day=Number(m.price_change_1d||0),week=Number(m.price_change_1w||0);
|
const signal=confirmedMarketSignal(m,textEvidenceSignal(m));
|
||||||
const anchor=Math.abs(day)>=0.006?Math.sign(day):Math.sign(week);
|
return {side:signal.side,strength:signal.strength,aligned:signal.type==="trend"&&signal.confirmed,type:signal.type};
|
||||||
if(!anchor)return {side:null,strength:0,aligned:false};
|
|
||||||
const contrary=Math.sign(day)&&Math.sign(week)&&Math.sign(day)!==Math.sign(week)&&Math.abs(day)>=0.012&&Math.abs(week)>=0.02;
|
|
||||||
const aligned=!contrary&&(Math.abs(day)>=0.006||Math.abs(week)>=0.012)&&(Math.abs(hour)<0.02||!Math.sign(hour)||Math.sign(hour)===anchor);
|
|
||||||
const strength=aligned?clamp(0.28+Math.abs(day)*3.2+Math.abs(week)*1.5+(Math.sign(day)===Math.sign(week)?0.18:0),0,1):0;
|
|
||||||
return {side:anchor>0?"YES":"NO",strength,aligned};
|
|
||||||
}
|
}
|
||||||
function effectiveEntryEdge(s){
|
function effectiveEntryEdge(s){
|
||||||
const net=Math.abs(Number(s&&s.net_edge));
|
const net=Math.abs(Number(s&&s.net_edge));
|
||||||
return Number.isFinite(net)?net:0;
|
return Number.isFinite(net)?net:0;
|
||||||
}
|
}
|
||||||
function reversalCandidate(s){
|
function reversalCandidate(s){
|
||||||
const day=Number(s&&s.price_change_1d||0),magnitude=Math.abs(day);
|
return s&&s.signal_type==="reversal"&&s.trade_ready?s:null;
|
||||||
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={}){
|
function analyzeMarket(m,realWorldSignals={}){
|
||||||
if(m.volume<MIN_SCOUT_VOLUME||m.liquidity<MIN_SCOUT_LIQUIDITY)return null;
|
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);
|
const external=realWorldSignals[m.id]||{},p=m.yes_price,policy=categoryPolicy(m.category);
|
||||||
const liq=liquiditySignal(m),mom=momentumSignal(m),timing=timingSignal(m.days_to_resolution),evidence=textEvidenceSignal(m,realWorldSignals[m.id]||{});
|
const liq=liquiditySignal(m),mom=momentumSignal(m),timing=timingSignal(m.days_to_resolution),evidence=textEvidenceSignal(m,external);
|
||||||
const friction=frictionPenalty(m),chase=chasePenalty(m,edgeYes),rawNet=Math.max(0,edge-friction-chase-policy.uncertainty);
|
const signal=confirmedMarketSignal(m,evidence),direction=signal.side==="YES"?1:(signal.side==="NO"?-1:0);
|
||||||
const netEdge=+(Math.sign(edgeYes)*rawNet).toFixed(4),absNet=Math.abs(netEdge);
|
const grossMove=signal.gross_move||0,friction=frictionPenalty(m),chase=chasePenalty(m,grossMove);
|
||||||
const mis=Math.min(1,absNet/EDGE_SCALE);
|
const rawNet=Math.max(0,grossMove-friction-chase-policy.uncertainty),edgeYes=direction*grossMove;
|
||||||
const conviction=+((W.LIQ*liq+W.MOM*mom+W.MIS*mis+W.TIME*timing)*82+evidence.score*18).toFixed(1);
|
const netEdge=+(direction*rawNet).toFixed(4),absNet=Math.abs(netEdge),fair=clamp(p+edgeYes,0.03,0.97);
|
||||||
const minEdge=Math.max(MIN_ENTRY_EDGE,policy.minEdge);
|
const signalScore=Math.min(1,absNet/EDGE_SCALE);
|
||||||
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 conviction=+((W.LIQ*liq+W.MOM*mom+W.SIGNAL*signal.confidence+W.TIME*timing)*100).toFixed(1);
|
||||||
const jumpRisk=fastSettlementRisk(m);
|
const jumpRisk=fastSettlementRisk(m);
|
||||||
const selectiveTradeReady=absNet>=MIN_SELECTIVE_ENTRY_EDGE&&conviction>=62&&m.volume>=MIN_VOLUME&&m.liquidity>=MIN_LIQUIDITY&&evidence.score>=0.50&&m.yes_price>=0.08&&m.yes_price<=0.92&&!jumpRisk;
|
const commonReady=signal.confirmed&&!jumpRisk&&direction!==0&&m.spread>0&&m.spread<=0.03
|
||||||
const trend=directionalPriceSignal(m);
|
&&m.volume>=MIN_VOLUME&&m.liquidity>=MIN_LIQUIDITY&&m.volume_24hr>=750
|
||||||
const trendTradeReady=!strictTradeReady&&!selectiveTradeReady&&absNet>=MIN_TREND_EDGE
|
&&m.days_to_resolution!=null&&m.days_to_resolution>=MIN_ENTRY_DAYS&&p>=0.08&&p<=0.92;
|
||||||
&&trend.aligned&&trend.strength>=0.65&&!jumpRisk&&evidence.score>=0.52
|
const strictTradeReady=commonReady&&absNet>=Math.max(MIN_ENTRY_EDGE,policy.minEdge)&&conviction>=70
|
||||||
&&m.spread>0&&m.spread<=0.025&&m.volume_24hr>=1500&&m.liquidity>=2500
|
&&m.volume>=Math.max(MIN_VOLUME,policy.minVol)&&m.liquidity>=Math.max(MIN_LIQUIDITY,policy.minLiq)&&evidence.score>=policy.minEvidence;
|
||||||
&&Math.abs(m.price_change_1d||0)<=0.10
|
const catalystTradeReady=!strictTradeReady&&commonReady&&evidence.source_count>=1&&evidence.score>=0.55
|
||||||
&&m.days_to_resolution!=null&&m.days_to_resolution>=MIN_ENTRY_DAYS&&p>=0.12&&p<=0.88;
|
&&absNet>=MIN_SELECTIVE_ENTRY_EDGE&&signal.confidence>=0.62;
|
||||||
const liquidityTradeReady=!strictTradeReady&&!selectiveTradeReady&&!trendTradeReady&&!jumpRisk
|
const reversalTradeReady=!strictTradeReady&&!catalystTradeReady&&commonReady&&signal.type==="reversal"
|
||||||
&&absNet>=MIN_LIQUIDITY_EDGE&&conviction>=68&&evidence.score>=0.54&&m.spread>0&&m.spread<=0.02
|
&&absNet>=MIN_LIQUIDITY_EDGE&&signal.confidence>=0.64&&m.spread<=0.025;
|
||||||
&&m.volume_24hr>=1000&&m.liquidity>=2000&&m.days_to_resolution!=null&&m.days_to_resolution>=MIN_ENTRY_DAYS
|
const liquidityTradeReady=!strictTradeReady&&!catalystTradeReady&&!reversalTradeReady&&commonReady&&signal.type==="trend"
|
||||||
&&p>=0.12&&p<=0.88&&Math.abs(m.price_change_1d||0)<=0.08;
|
&&absNet>=MIN_LIQUIDITY_EDGE&&signal.confidence>=0.68&&evidence.score>=0.50&&m.spread<=0.018&&m.liquidity>=5000;
|
||||||
const coreProbeTradeReady=!strictTradeReady&&!selectiveTradeReady&&!trendTradeReady&&!liquidityTradeReady&&!jumpRisk
|
const trendTradeReady=!strictTradeReady&&!catalystTradeReady&&!reversalTradeReady&&!liquidityTradeReady&&commonReady&&signal.type==="trend"
|
||||||
&&absNet>=MIN_CORE_PROBE_EDGE&&conviction>=62&&evidence.score>=0.65
|
&&absNet>=Math.max(MIN_TREND_EDGE,policy.minEdge)&&signal.confidence>=0.60&&evidence.score>=policy.minEvidence;
|
||||||
&&m.spread>0&&m.spread<=0.025&&m.volume_24hr>=25000&&m.liquidity>=10000
|
const tradeReady=strictTradeReady||catalystTradeReady||reversalTradeReady||liquidityTradeReady||trendTradeReady;
|
||||||
&&m.days_to_resolution!=null&&m.days_to_resolution>=14&&p>=0.12&&p<=0.88
|
const quality=strictTradeReady?"confirmed":(catalystTradeReady?"catalyst":(reversalTradeReady?"reversal":(liquidityTradeReady?"liquid-trend":(trendTradeReady?"trend":"watch"))));
|
||||||
&&Math.abs(m.price_change_1d||0)<=0.08;
|
const side=signal.side||((Number(m.price_change_1d||0)>=0)?"YES":"NO");
|
||||||
const speculativeTradeReady=!strictTradeReady&&!selectiveTradeReady&&!trendTradeReady&&!liquidityTradeReady&&!coreProbeTradeReady&&!jumpRisk
|
const entry=side==="YES"?p:m.no_price;
|
||||||
&&absNet>=MIN_AGGRESSIVE_EDGE&&conviction>=55&&evidence.score>=0.48
|
let rationale;
|
||||||
&&m.spread>0&&m.spread<=0.03&&m.volume_24hr>=750&&m.liquidity>=1500
|
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.`;}
|
||||||
&&m.days_to_resolution!=null&&m.days_to_resolution>=10&&p>=0.10&&p<=0.90
|
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.`;}
|
||||||
&&Math.abs(m.price_change_1d||0)<=0.12&&(trend.strength>=0.50||evidence.source_count>=1);
|
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.`;}
|
||||||
const allocationTradeReady=!strictTradeReady&&!selectiveTradeReady&&!trendTradeReady&&!liquidityTradeReady&&!coreProbeTradeReady&&!speculativeTradeReady&&!jumpRisk
|
else if(liquidityTradeReady){rationale=`Liquid trend: ${side} is aligned across one-day and one-week windows in a tight, deep market with a ${(absNet*100).toFixed(1)}c post-friction signal margin.`;}
|
||||||
&&absNet>=MIN_ALLOCATION_EDGE&&conviction>=50&&evidence.score>=0.46
|
else if(trendTradeReady){rationale=`Confirmed trend: ${side} is aligned across one-day and one-week windows, avoids chase conditions, and retains a ${(absNet*100).toFixed(1)}c post-friction signal margin.`;}
|
||||||
&&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||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.`;}
|
|
||||||
else if(jumpRisk){rationale="Watch only: this market can jump directly to settlement before an hourly stop can protect the position, so agents will not buy it.";}
|
else if(jumpRisk){rationale="Watch only: this market can jump directly to settlement before an hourly stop can protect the position, so agents will not buy it.";}
|
||||||
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.`;}
|
else if(!signal.side){rationale="Watch only: no direction is confirmed across independent time windows. Recent price movement alone is not treated as fair value.";}
|
||||||
|
else{rationale=`Watch only: the ${signal.type} setup leaves ${(absNet*100).toFixed(1)}c after friction and uncertainty, but it has not cleared every confirmation, liquidity, evidence, and timing gate.`;}
|
||||||
const drivers=[policy.label];
|
const drivers=[policy.label];
|
||||||
if(liq>0.6)drivers.push("deep/liquid market");
|
if(liq>0.6)drivers.push("deep/liquid market");
|
||||||
if(mom>0.6)drivers.push("strong fresh volume (24h surge)");
|
if(mom>0.6)drivers.push("strong fresh volume (24h surge)");
|
||||||
if(timing>0.8)drivers.push("resolves in a good window");
|
if(timing>0.8)drivers.push("resolves in a good window");
|
||||||
if(mis>0.4)drivers.push("net price/value gap after costs");
|
if(signal.type==="trend")drivers.push("one-day and one-week direction agree");
|
||||||
|
if(signal.type==="reversal")drivers.push("hourly reversal confirms the snapback");
|
||||||
|
if(signalScore>0.4)drivers.push("positive signal margin after modeled friction");
|
||||||
drivers.push(...evidence.drivers.slice(0,3));
|
drivers.push(...evidence.drivers.slice(0,3));
|
||||||
if(!drivers.length)drivers.push("thin signal");
|
if(!drivers.length)drivers.push("thin signal");
|
||||||
return {market_id:m.id,question:m.question,event:m.event,url:m.url,category:m.category,tags:m.tags,
|
return {market_id:m.id,question:m.question,event:m.event,url:m.url,category:m.category,tags:m.tags,
|
||||||
clob_yes:(m.clob_token_ids||[])[0]||null,clob_no:(m.clob_token_ids||[])[1]||null,
|
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),
|
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,
|
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":(allocationTradeReady?"allocation":(explorationTradeReady?"explore":"watch"))))))),
|
quality,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:+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,
|
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,
|
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,
|
||||||
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)+"%";
|
||||||
@@ -1229,7 +1214,7 @@ function suggestionBand(s){
|
|||||||
}
|
}
|
||||||
function marketWatchSuggestion(m,realWorldSignals={}){
|
function marketWatchSuggestion(m,realWorldSignals={}){
|
||||||
if(!m||!m.question)return null;
|
if(!m||!m.question)return null;
|
||||||
const p=m.yes_price,fair=fairValue(m),edgeYes=fair-p;
|
const p=m.yes_price,fair=fairValue(m,realWorldSignals[m.id]||{}),edgeYes=fair-p;
|
||||||
const liq=liquiditySignal(m),mom=momentumSignal(m),timing=timingSignal(m.days_to_resolution),policy=categoryPolicy(m.category),evidence=textEvidenceSignal(m,realWorldSignals[m.id]||{});
|
const liq=liquiditySignal(m),mom=momentumSignal(m),timing=timingSignal(m.days_to_resolution),policy=categoryPolicy(m.category),evidence=textEvidenceSignal(m,realWorldSignals[m.id]||{});
|
||||||
const friction=frictionPenalty(m),chase=chasePenalty(m,edgeYes),rawNet=Math.max(0,Math.abs(edgeYes)-friction-chase-policy.uncertainty),netEdge=+(Math.sign(edgeYes)*rawNet).toFixed(4);
|
const friction=frictionPenalty(m),chase=chasePenalty(m,edgeYes),rawNet=Math.max(0,Math.abs(edgeYes)-friction-chase-policy.uncertainty),netEdge=+(Math.sign(edgeYes)*rawNet).toFixed(4);
|
||||||
const side=edgeYes>=0?"YES":"NO";
|
const side=edgeYes>=0?"YES":"NO";
|
||||||
@@ -1247,7 +1232,7 @@ function marketWatchSuggestion(m,realWorldSignals={}){
|
|||||||
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,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 full Polymarket scan. Net edge is ${(Math.abs(netEdge)*100).toFixed(1)}c and evidence score is ${Math.round(evidence.score*100)}, so agents will not buy it unless conditions improve.`};
|
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.`};
|
||||||
}
|
}
|
||||||
function generateSuggestions(markets,total=SUGGESTION_TOTAL,perCategory=SUGGESTION_PER_CATEGORY,realWorldSignals={}){
|
function generateSuggestions(markets,total=SUGGESTION_TOTAL,perCategory=SUGGESTION_PER_CATEGORY,realWorldSignals={}){
|
||||||
const actionable=markets.map(m=>analyzeMarket(m,realWorldSignals)).filter(Boolean).filter(a=>a.side!=="HOLD").sort((a,b)=>b.conviction-a.conviction);
|
const actionable=markets.map(m=>analyzeMarket(m,realWorldSignals)).filter(Boolean).filter(a=>a.side!=="HOLD").sort((a,b)=>b.conviction-a.conviction);
|
||||||
@@ -1270,10 +1255,11 @@ function generateSuggestions(markets,total=SUGGESTION_TOTAL,perCategory=SUGGESTI
|
|||||||
|
|
||||||
/* ---------- Multi-agent store ---------- */
|
/* ---------- Multi-agent store ---------- */
|
||||||
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],closed:[],history:[],snapshots:[],stopped:{},lastDecision:null};}
|
function defaultPortfolio(){return {cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],closed:[],history:[],snapshots:[],stopped:{},lastDecision:null};}
|
||||||
function defaultState(){const agents={};AGENTS.forEach(a=>agents[a.id]=defaultPortfolio());return {date:null,last_run:null,last_cycle_hour:null,agents,seeded:false};}
|
function defaultState(){const agents={};AGENTS.forEach(a=>agents[a.id]=defaultPortfolio());return {date:null,last_run:null,last_cycle_hour:null,engine_version:SUGGESTION_ENGINE_VERSION,engine_started_at:nowIso(),agents,seeded:false};}
|
||||||
function loadState(){
|
function loadState(){
|
||||||
let st; try{st=JSON.parse(localStorage.getItem(AGENTS_KEY));}catch(e){st=null;}
|
let st; try{st=JSON.parse(localStorage.getItem(AGENTS_KEY));}catch(e){st=null;}
|
||||||
if(!st||!st.agents)st=defaultState();
|
if(!st||!st.agents)st=defaultState();
|
||||||
|
let migrated=false;
|
||||||
const activeIds=new Set(AGENTS.map(a=>a.id));
|
const activeIds=new Set(AGENTS.map(a=>a.id));
|
||||||
Object.keys(st.agents).forEach(id=>{if(!activeIds.has(id))delete st.agents[id];});
|
Object.keys(st.agents).forEach(id=>{if(!activeIds.has(id))delete st.agents[id];});
|
||||||
delete st.whales;
|
delete st.whales;
|
||||||
@@ -1283,7 +1269,19 @@ function loadState(){
|
|||||||
if(!st.agents[a.id].stopped)st.agents[a.id].stopped={};
|
if(!st.agents[a.id].stopped)st.agents[a.id].stopped={};
|
||||||
if(!("lastDecision" in st.agents[a.id]))st.agents[a.id].lastDecision=null;
|
if(!("lastDecision" in st.agents[a.id]))st.agents[a.id].lastDecision=null;
|
||||||
});
|
});
|
||||||
|
if(st.engine_version!==SUGGESTION_ENGINE_VERSION){
|
||||||
|
const started=nowIso();
|
||||||
|
st.previous_engine_version=st.engine_version||null;
|
||||||
|
st.engine_version=SUGGESTION_ENGINE_VERSION;
|
||||||
|
st.engine_started_at=started;
|
||||||
|
AGENTS.forEach(a=>{
|
||||||
|
const p=st.agents[a.id],eq=Number(p.cash||0)+(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||Number(pos.shares||0)*Number(pos.current_price||0)),0);
|
||||||
|
p.engine_baseline={version:SUGGESTION_ENGINE_VERSION,started_at:started,equity:+eq.toFixed(2)};
|
||||||
|
});
|
||||||
|
migrated=true;
|
||||||
|
}
|
||||||
if(!st.last_cycle_hour&&st.last_run)st.last_cycle_hour=cycleHourFromIso(st.last_run);
|
if(!st.last_cycle_hour&&st.last_run)st.last_cycle_hour=cycleHourFromIso(st.last_run);
|
||||||
|
if(migrated)localStorage.setItem(AGENTS_KEY,JSON.stringify(compactAgentStateForSync(st)));
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
function compactPortfolioForSync(p,limits=SYNC_LIMITS){
|
function compactPortfolioForSync(p,limits=SYNC_LIMITS){
|
||||||
@@ -1315,6 +1313,7 @@ function compactSuggestionForSync(s){
|
|||||||
net_edge:s.net_edge,friction:s.friction,chase_penalty:s.chase_penalty,evidence_score:s.evidence_score,evidence_source_count:s.evidence_source_count,quality:s.quality,
|
net_edge:s.net_edge,friction:s.friction,chase_penalty:s.chase_penalty,evidence_score:s.evidence_score,evidence_source_count:s.evidence_source_count,quality:s.quality,
|
||||||
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,
|
||||||
trade_ready:s.trade_ready,watch_only:s.watch_only,jump_risk:s.jump_risk,
|
trade_ready:s.trade_ready,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,
|
||||||
};
|
};
|
||||||
@@ -1522,44 +1521,21 @@ function adaptiveDecision(cfg,p,rank,total,leaderEq){
|
|||||||
const trend=recentReturnDelta(p);
|
const trend=recentReturnDelta(p);
|
||||||
const emo=emotionalState(ret,trail,trend,rank);
|
const emo=emotionalState(ret,trail,trend,rank);
|
||||||
const aggressive=!!cfg.aggressive;
|
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 minConv=Math.max(cfg.minConv??0,aggressive?60:64),maxNew=Math.min(cfg.maxNew??4,aggressive?4:3),maxFrac=Math.min(cfg.maxFrac??0.05,aggressive?0.07:0.045),reserve=aggressive?0.10:0.18;
|
||||||
let targetExposure=cfg.targetExposure??(aggressive?0.80:0.62);
|
let targetExposure=cfg.targetExposure??(aggressive?0.80:0.62);
|
||||||
let mode=aggressive?"Aggressive Hunt":"Quality First";
|
let mode=aggressive?"Confirmed Opportunity":"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.";
|
let reason=`exposure is capped at ${Math.round(targetExposure*100)}%; cash is acceptable when no independently confirmed setup clears this strategy's rules.`;
|
||||||
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;
|
|
||||||
reason="recent momentum is positive, so it presses the advantage while risk caps stay active.";
|
|
||||||
}
|
|
||||||
if(ret<-5){
|
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;
|
mode="Drawdown Quality Control";minConv+=3;maxNew=Math.min(maxNew,2);maxFrac*=0.78;reserve=Math.max(reserve,aggressive?0.16:0.24);
|
||||||
targetExposure=Math.max(aggressive?0.60:0.38,targetExposure-(aggressive?0.05:0.08));
|
targetExposure=Math.max(aggressive?0.52:0.38,targetExposure-0.10);
|
||||||
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.";
|
reason="in drawdown, so it requires stronger confirmation and smaller entries instead of attempting a forced recovery.";
|
||||||
}
|
}
|
||||||
if(ret<-18){
|
if(ret<-18){
|
||||||
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;
|
mode="Drawdown Repair";minConv+=4;maxNew=1;maxFrac=Math.min(maxFrac,aggressive?0.045:0.03);reserve=aggressive?0.25:0.32;
|
||||||
targetExposure=cfg.drawdownExposure??(aggressive?0.65:0.42);
|
targetExposure=Math.min(targetExposure,cfg.drawdownExposure??(aggressive?0.55:0.40));
|
||||||
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.";
|
reason="down more than 18%, so it can add only one small, strongly confirmed position. It will not revenge trade.";
|
||||||
}
|
}
|
||||||
if(emo.mood==="impatient"||emo.mood==="frustrated"){
|
reason+=` Emotion is reported as ${emo.label.toLowerCase()} but cannot increase size, reduce confirmation, or add attack slots.`;
|
||||||
if(aggressive)maxNew+=1;else minConv+=1;
|
|
||||||
reason+=aggressive?` Emotion layer: ${emo.label.toLowerCase()} adds one qualified attack slot but cannot weaken the positive-edge rule.`:` Emotion layer: ${emo.label.toLowerCase()} is reported, but it cannot loosen entry standards or increase size.`;
|
|
||||||
}else if(emo.mood==="confident"){
|
|
||||||
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,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;
|
|
||||||
if((p.cash/Math.max(eq,1))<operatingReserve){maxNew=Math.max(1,Math.min(maxNew,aggressive?3:2));reason+=` Cash is below the ${Math.round(operatingReserve*1000)/10}% operating reserve, so new entries are temporarily rationed.`;}
|
|
||||||
const todaySnaps=(p.snapshots||[]).filter(s=>s.date===todayStr());
|
const todaySnaps=(p.snapshots||[]).filter(s=>s.date===todayStr());
|
||||||
const cycleStart=todaySnaps.length?Number(todaySnaps[0].equity||eq):eq;
|
const cycleStart=todaySnaps.length?Number(todaySnaps[0].equity||eq):eq;
|
||||||
const cycleDrawdown=cycleStart>0?(eq/cycleStart-1)*100:0;
|
const cycleDrawdown=cycleStart>0?(eq/cycleStart-1)*100:0;
|
||||||
@@ -1570,9 +1546,7 @@ function adaptiveDecision(cfg,p,rank,total,leaderEq){
|
|||||||
}
|
}
|
||||||
const maxPositionPct=aggressive?0.10:MAX_NEW_POSITION_PCT;
|
const maxPositionPct=aggressive?0.10:MAX_NEW_POSITION_PCT;
|
||||||
targetExposure=clamp(targetExposure,0,1-reserve);
|
targetExposure=clamp(targetExposure,0,1-reserve);
|
||||||
const minExposure=Math.max(0,Math.min(targetExposure,targetExposure-(aggressive?0.14:0.16)));
|
const minExposure=0,belowFloor=false;
|
||||||
const belowFloor=currentExposure+0.001<minExposure;
|
|
||||||
if(belowFloor&&maxNew>0)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,
|
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};
|
currentExposure:+currentExposure.toFixed(3),targetExposure:+targetExposure.toFixed(3),minExposure:+minExposure.toFixed(3),belowFloor};
|
||||||
}
|
}
|
||||||
@@ -1611,16 +1585,22 @@ function triggeredStopLosses(pos){
|
|||||||
}
|
}
|
||||||
function doneGainStops(pos){
|
function doneGainStops(pos){
|
||||||
const done=pos.gain_stops&&typeof pos.gain_stops==="object"?Object.assign({},pos.gain_stops):{};
|
const done=pos.gain_stops&&typeof pos.gain_stops==="object"?Object.assign({},pos.gain_stops):{};
|
||||||
if(pos.took_profit_at&&!done["75"]&&!done["150"]){
|
if(done["75"]&&!done.lock1)done.lock1=done["75"];
|
||||||
done["75"]=pos.took_profit_at;
|
if(done["150"]&&!done.lock2)done.lock2=done["150"];
|
||||||
done["150"]=pos.took_profit_at;
|
if(done["300"]&&!done.lock3)done.lock3=done["300"];
|
||||||
}
|
if(pos.took_profit_at&&!done.lock1)done.lock1=pos.took_profit_at;
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
|
function gainStopReturn(pos,tier){
|
||||||
|
const entry=Number(pos.entry_price);
|
||||||
|
if(!Number.isFinite(entry)||entry<=0||entry>=1)return null;
|
||||||
|
const maximumReturn=(1-entry)/entry;
|
||||||
|
return Math.max(0.01,Math.min(tier.gain,maximumReturn*tier.maxGainFrac));
|
||||||
|
}
|
||||||
function gainStopTarget(pos,tier){
|
function gainStopTarget(pos,tier){
|
||||||
const entry=Number(pos.entry_price);
|
const entry=Number(pos.entry_price);
|
||||||
if(Number.isFinite(entry)&&entry>0)return +(entry*tier.multiple).toFixed(4);
|
const targetReturn=gainStopReturn(pos,tier);
|
||||||
if(pos.shares>0&&pos.cost>0)return +((pos.cost/pos.shares)*tier.multiple).toFixed(4);
|
if(Number.isFinite(entry)&&entry>0&&targetReturn!=null)return +Math.min(0.999,entry*(1+targetReturn)).toFixed(4);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function nextGainStop(pos){
|
function nextGainStop(pos){
|
||||||
@@ -1644,9 +1624,9 @@ function gainStopLabel(pos){
|
|||||||
const target=gainStopTarget(pos,tier);
|
const target=gainStopTarget(pos,tier);
|
||||||
const prefix=doneCount?`Gain stop: ${doneCount*25}% sold; next `:"Gain stop: ";
|
const prefix=doneCount?`Gain stop: ${doneCount*25}% sold; next `:"Gain stop: ";
|
||||||
if(target==null)return `${prefix}waiting for ${tier.label}`;
|
if(target==null)return `${prefix}waiting for ${tier.label}`;
|
||||||
if(target>1)return `${prefix}${tier.label} unreachable from ${pct(pos.entry_price)} entry`;
|
|
||||||
const gap=Math.max(0,target-(pos.current_price||0));
|
const gap=Math.max(0,target-(pos.current_price||0));
|
||||||
return `${prefix}sell 25% at ${pct(target)} (${tier.label})${gap>0?` — ${(gap*100).toFixed(1)}c away`:" — armed"}`;
|
const targetReturn=gainStopReturn(pos,tier);
|
||||||
|
return `${prefix}sell 25% at ${pct(target)} (${tier.label}, +${(targetReturn*100).toFixed(1)}%)${gap>0?` — ${(gap*100).toFixed(1)}c away`:" — armed"}`;
|
||||||
}
|
}
|
||||||
function stopLossLabel(pos){
|
function stopLossLabel(pos){
|
||||||
const done=doneStopLosses(pos),doneCount=STOP_LOSS_TIERS.filter(t=>done[t.id]).length;
|
const done=doneStopLosses(pos),doneCount=STOP_LOSS_TIERS.filter(t=>done[t.id]).length;
|
||||||
@@ -1660,7 +1640,7 @@ function stopOutPosition(p,pos){
|
|||||||
const proceeds=+(pos.shares*pos.current_price).toFixed(2);
|
const proceeds=+(pos.shares*pos.current_price).toFixed(2);
|
||||||
p.cash=+(p.cash+proceeds).toFixed(2);
|
p.cash=+(p.cash+proceeds).toFixed(2);
|
||||||
pos.exit_price=pos.current_price;pos.closed_at=cycleIso();pos.close_reason="Stop loss -18%";pos.close_action="STOP";
|
pos.exit_price=pos.current_price;pos.closed_at=cycleIso();pos.close_reason="Stop loss -18%";pos.close_action="STOP";
|
||||||
pos.realized_pnl=+(proceeds-pos.cost).toFixed(2);
|
pos.realized_pnl=+(proceeds-pos.cost+Number(pos.partial_realized_pnl||0)).toFixed(2);
|
||||||
p.closed.push(pos);rememberStop(p,pos);
|
p.closed.push(pos);rememberStop(p,pos);
|
||||||
p.history.push({date:logDay(),action:"STOP",question:pos.question,side:pos.side,
|
p.history.push({date:logDay(),action:"STOP",question:pos.question,side:pos.side,
|
||||||
detail:`Stop-loss sold '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`});
|
detail:`Stop-loss sold '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`});
|
||||||
@@ -1687,6 +1667,7 @@ function scaleStopLossPosition(p,pos,tier){
|
|||||||
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
||||||
pos.stop_losses=doneStopLosses(pos);
|
pos.stop_losses=doneStopLosses(pos);
|
||||||
pos.stop_losses[tier.id]=cycleIso();
|
pos.stop_losses[tier.id]=cycleIso();
|
||||||
|
pos.partial_realized_pnl=+(Number(pos.partial_realized_pnl||0)+realizedPnl).toFixed(2);
|
||||||
p.history.push({date:logDay(),action:"STOP",question:pos.question,side:pos.side,
|
p.history.push({date:logDay(),action:"STOP",question:pos.question,side:pos.side,
|
||||||
detail:`Stop-loss ${tier.label} sold 33% of '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (realized ${fmtUSD(realizedPnl)})`});
|
detail:`Stop-loss ${tier.label} sold 33% of '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (realized ${fmtUSD(realizedPnl)})`});
|
||||||
}
|
}
|
||||||
@@ -1694,7 +1675,7 @@ function closePosition(p,pos,reason,action="CLOSE"){
|
|||||||
const proceeds=+(pos.shares*pos.current_price).toFixed(2);
|
const proceeds=+(pos.shares*pos.current_price).toFixed(2);
|
||||||
p.cash=+(p.cash+proceeds).toFixed(2);
|
p.cash=+(p.cash+proceeds).toFixed(2);
|
||||||
pos.exit_price=pos.current_price;pos.closed_at=cycleIso();pos.close_reason=reason;pos.close_action=action;
|
pos.exit_price=pos.current_price;pos.closed_at=cycleIso();pos.close_reason=reason;pos.close_action=action;
|
||||||
pos.realized_pnl=+(proceeds-pos.cost).toFixed(2);
|
pos.realized_pnl=+(proceeds-pos.cost+Number(pos.partial_realized_pnl||0)).toFixed(2);
|
||||||
p.closed.push(pos);
|
p.closed.push(pos);
|
||||||
p.history.push({date:logDay(),action,question:pos.question,side:pos.side,
|
p.history.push({date:logDay(),action,question:pos.question,side:pos.side,
|
||||||
detail:`${reason} '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`});
|
detail:`${reason} '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`});
|
||||||
@@ -1717,8 +1698,9 @@ function takeProfitPosition(p,pos,tier){
|
|||||||
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
||||||
pos.gain_stops=doneGainStops(pos);
|
pos.gain_stops=doneGainStops(pos);
|
||||||
pos.gain_stops[tier.id]=cycleIso();
|
pos.gain_stops[tier.id]=cycleIso();
|
||||||
|
pos.partial_realized_pnl=+(Number(pos.partial_realized_pnl||0)+realizedPnl).toFixed(2);
|
||||||
p.history.push({date:logDay(),action:"GAIN",question:pos.question,side:pos.side,
|
p.history.push({date:logDay(),action:"GAIN",question:pos.question,side:pos.side,
|
||||||
detail:`Gain stop ${tier.label} sold 25% of '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (realized ${fmtUSD(realizedPnl)})`});
|
detail:`${tier.label} sold 25% of '${pos.question.slice(0,40)}' at ${pct(pos.current_price)} for ${fmtUSD(proceeds)} (realized ${fmtUSD(realizedPnl)})`});
|
||||||
}
|
}
|
||||||
function daysHeld(pos){
|
function daysHeld(pos){
|
||||||
if(!pos.opened_at)return 0;
|
if(!pos.opened_at)return 0;
|
||||||
@@ -1726,19 +1708,37 @@ function daysHeld(pos){
|
|||||||
if(!Number.isFinite(start)||!Number.isFinite(end))return 0;
|
if(!Number.isFinite(start)||!Number.isFinite(end))return 0;
|
||||||
return Math.max(0,(end-start)/86400000);
|
return Math.max(0,(end-start)/86400000);
|
||||||
}
|
}
|
||||||
|
function hoursSince(iso){
|
||||||
|
const start=new Date(iso||"").getTime(),end=new Date(cycleIso()).getTime();
|
||||||
|
return Number.isFinite(start)&&Number.isFinite(end)?Math.max(0,(end-start)/3600000):0;
|
||||||
|
}
|
||||||
|
function trailingProfitReason(pos){
|
||||||
|
const entry=Number(pos.entry_price),current=Number(pos.current_price),peak=Number(pos.peak_price||current);
|
||||||
|
if(!entry||!current||!peak)return null;
|
||||||
|
const peakReturn=peak/entry-1,currentReturn=current/entry-1;
|
||||||
|
const firstTarget=gainStopReturn(pos,GAIN_STOP_TIERS[0]);
|
||||||
|
if(firstTarget==null||peakReturn<firstTarget)return null;
|
||||||
|
const retainedFloor=Math.max(0.02,peakReturn*0.55);
|
||||||
|
return currentReturn<=retainedFloor?`Trailing profit lock after a ${(peakReturn*100).toFixed(1)}% peak`:null;
|
||||||
|
}
|
||||||
function exitReason(pos,fresh,analysis,cfg){
|
function exitReason(pos,fresh,analysis,cfg){
|
||||||
if(fastSettlementRisk(fresh))return "Risk policy removed fast-settling event exposure";
|
if(fastSettlementRisk(fresh))return "Risk policy removed fast-settling event exposure";
|
||||||
if(fresh.days_to_resolution!=null&&fresh.days_to_resolution<=EXIT_RESOLUTION_DAYS)return `Close before resolution (${fresh.days_to_resolution.toFixed(1)}d left)`;
|
const trail=trailingProfitReason(pos);if(trail)return trail;
|
||||||
if(daysHeld(pos)>=EXIT_STALE_DAYS)return `Stale exit after ${Math.floor(daysHeld(pos))} days`;
|
const heldHours=daysHeld(pos)*24;
|
||||||
const entryEdge=Math.abs(Number(pos.net_edge||0));
|
if(daysHeld(pos)>=EXIT_STALE_DAYS&&(pos.unrealized_pnl||0)<=0)return `Stale losing exit after ${Math.floor(daysHeld(pos))} days`;
|
||||||
if(pos.quality==="explore")return "Quality audit retired zero-edge exploration exposure";
|
|
||||||
if(pos.quality==="trend"&&entryEdge<MIN_TREND_EDGE)return "Quality audit retired a trend trade without enough post-cost edge";
|
|
||||||
if(pos.quality==="liquidity"&&entryEdge<MIN_LIQUIDITY_EDGE)return "Quality audit retired a liquidity trade without enough post-cost edge";
|
|
||||||
if(!analysis)return null;
|
if(!analysis)return null;
|
||||||
const net=Math.abs(analysis.net_edge!=null?analysis.net_edge:analysis.edge||0);
|
const net=Math.abs(analysis.net_edge!=null?analysis.net_edge:analysis.edge||0);
|
||||||
if(analysis.side&&analysis.side!==pos.side&&net>=MIN_ENTRY_EDGE)return `Model flipped to ${analysis.side}`;
|
const conflicting=analysis.trade_ready&&analysis.side&&analysis.side!==pos.side&&net>=MIN_SELECTIVE_ENTRY_EDGE;
|
||||||
|
if(conflicting){
|
||||||
|
if(!pos.signal_conflict_since)pos.signal_conflict_since=cycleIso();
|
||||||
|
if(heldHours>=MIN_POLICY_HOLD_HOURS&&hoursSince(pos.signal_conflict_since)>=EXIT_CONFIRM_HOURS)return `Confirmed signal reversal to ${analysis.side}`;
|
||||||
|
}else delete pos.signal_conflict_since;
|
||||||
const minExit=Math.max(30,(cfg&&cfg.minConv?cfg.minConv:45)-8);
|
const minExit=Math.max(30,(cfg&&cfg.minConv?cfg.minConv:45)-8);
|
||||||
if((pos.unrealized_pnl||0)<0&&analysis.conviction<minExit&&net<MIN_ENTRY_EDGE*0.25)return `Conviction faded to ${Math.round(analysis.conviction)}`;
|
const faded=(pos.unrealized_pnl||0)<0&&analysis.conviction<minExit&&!analysis.trade_ready;
|
||||||
|
if(faded){
|
||||||
|
if(!pos.signal_fade_since)pos.signal_fade_since=cycleIso();
|
||||||
|
if(heldHours>=48&&hoursSince(pos.signal_fade_since)>=12)return `Signal remained unconfirmed for 12 hours`;
|
||||||
|
}else delete pos.signal_fade_since;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function markToMarket(p,priceMap,cfg=null,{policyExits=false}={}){
|
function markToMarket(p,priceMap,cfg=null,{policyExits=false}={}){
|
||||||
@@ -1753,6 +1753,7 @@ function markToMarket(p,priceMap,cfg=null,{policyExits=false}={}){
|
|||||||
const price=pos.side==="YES"?fresh.yes_price:fresh.no_price;
|
const price=pos.side==="YES"?fresh.yes_price:fresh.no_price;
|
||||||
pos.current_price=+price.toFixed(4);pos.value=+(pos.shares*price).toFixed(2);
|
pos.current_price=+price.toFixed(4);pos.value=+(pos.shares*price).toFixed(2);
|
||||||
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2);
|
||||||
|
pos.peak_price=+Math.max(Number(pos.peak_price||pos.entry_price||price),price).toFixed(4);
|
||||||
delete pos.price_status;
|
delete pos.price_status;
|
||||||
for(const stopTier of triggeredStopLosses(pos))scaleStopLossPosition(p,pos,stopTier);
|
for(const stopTier of triggeredStopLosses(pos))scaleStopLossPosition(p,pos,stopTier);
|
||||||
if(pos._closedByStop){delete pos._closedByStop;continue;}
|
if(pos._closedByStop){delete pos._closedByStop;continue;}
|
||||||
@@ -1840,9 +1841,7 @@ function reduceStrategyOverlap(st){
|
|||||||
});
|
});
|
||||||
Object.values(groups).filter(g=>g.length>1).forEach(group=>{
|
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)));
|
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","allocation","explore"].includes(pos.quality));
|
group.slice(2).forEach(({p,pos})=>closePosition(p,pos,"Strict overlap guard limited this market to two agents","EXIT"));
|
||||||
const coreProbe=group.some(({pos})=>pos.quality==="core-probe");
|
|
||||||
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=>{
|
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);
|
const p=st.agents&&st.agents[cfg.id]; if(p)p.positions=(p.positions||[]).filter(pos=>!pos.closed_at);
|
||||||
@@ -1850,56 +1849,39 @@ function reduceStrategyOverlap(st){
|
|||||||
}
|
}
|
||||||
function agentAcceptsSuggestion(cfg,s){
|
function agentAcceptsSuggestion(cfg,s){
|
||||||
const quality=s.quality||"watch",edge=effectiveEntryEdge(s),evidence=Number(s.evidence_score||0);
|
const quality=s.quality||"watch",edge=effectiveEntryEdge(s),evidence=Number(s.evidence_score||0);
|
||||||
if(quality==="explore"||quality==="watch")return false;
|
if(quality==="watch")return false;
|
||||||
if(cfg.id==="value")return (["EV+","selective"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE)
|
if(cfg.id==="value")return ["confirmed","liquid-trend"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE
|
||||||
||(quality==="core-probe"&&edge>=0.017&&evidence>=0.68)
|
&&Number(s.entry_price||0)>=0.20&&Number(s.entry_price||0)<=0.80;
|
||||||
||(quality==="allocation"&&edge>=0.010&&evidence>=0.46);
|
if(cfg.id==="momentum")return ["confirmed","trend","liquid-trend","catalyst"].includes(quality)
|
||||||
if(cfg.id==="momentum")return ["EV+","selective"].includes(quality)
|
&&s.signal_type==="trend"&&Number(s.signal_strength||0)>=0.60&&evidence>=0.48;
|
||||||
||(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==="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
|
if(cfg.id==="favorite")return Number(s.entry_price||0)>=0.62
|
||||||
&&((["EV+","selective","liquidity","core-probe"].includes(quality)&&edge>=MIN_CORE_PROBE_EDGE)
|
&&["confirmed","liquid-trend","catalyst"].includes(quality)&&edge>=MIN_LIQUIDITY_EDGE&&evidence>=0.50;
|
||||||
||(quality==="allocation"&&edge>=0.008&&evidence>=0.46));
|
|
||||||
if(cfg.id==="longshot")return Number(s.entry_price||0)<=0.48
|
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)
|
&&["confirmed","catalyst","reversal"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE&&evidence>=0.52;
|
||||||
||(quality==="allocation"&&edge>=0.010&&evidence>=0.48));
|
if(cfg.id==="diversifier")return ["confirmed","liquid-trend","catalyst","reversal"].includes(quality)
|
||||||
if(cfg.id==="diversifier")return ["EV+","selective","liquidity"].includes(quality)&&edge>=MIN_LIQUIDITY_EDGE
|
&&edge>=MIN_LIQUIDITY_EDGE&&evidence>=0.50;
|
||||||
||(quality==="trend"&&edge>=0.020&&evidence>=0.58)
|
if(cfg.id==="catalyst")return ["catalyst","confirmed"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE
|
||||||
||(quality==="core-probe"&&edge>=MIN_CORE_PROBE_EDGE&&evidence>=0.68)
|
&&Number(s.evidence_source_count||0)>=1&&Number(s.signal_confidence||0)>=0.62;
|
||||||
||(quality==="allocation"&&edge>=0.008&&evidence>=0.46);
|
if(cfg.id==="reversal")return ["reversal","confirmed"].includes(quality)&&s.signal_type==="reversal"
|
||||||
if(cfg.id==="catalyst")return ["EV+","selective","trend","liquidity","speculative","allocation"].includes(quality)
|
&&edge>=MIN_LIQUIDITY_EDGE&&Number(s.signal_confidence||0)>=0.64;
|
||||||
&&edge>=MIN_AGGRESSIVE_EDGE&&(Number(s.evidence_source_count||0)>=1||evidence>=0.62);
|
if(cfg.id==="breakout")return ["confirmed","trend","liquid-trend","catalyst"].includes(quality)
|
||||||
if(cfg.id==="reversal"){
|
&&s.signal_type==="trend"&&edge>=MIN_TREND_EDGE&&Number(s.signal_strength||0)>=0.64&&evidence>=0.48;
|
||||||
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","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","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.42
|
if(cfg.id==="tailalpha")return Number(s.entry_price||0)<=0.42
|
||||||
&&["EV+","selective","trend","speculative","allocation"].includes(quality)
|
&&["confirmed","catalyst"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE&&evidence>=0.55;
|
||||||
&&edge>=(quality==="allocation"?0.008:0.012)&&evidence>=(quality==="allocation"?0.48:0.55);
|
if(cfg.id==="conviction")return ["confirmed","catalyst"].includes(quality)
|
||||||
if(cfg.id==="conviction")return ["EV+","selective","speculative","allocation"].includes(quality)
|
&&edge>=MIN_ENTRY_EDGE&&Number(s.conviction||0)>=72&&Number(s.signal_confidence||0)>=0.66&&evidence>=0.52;
|
||||||
&&edge>=(quality==="allocation"?0.010:0.015)&&Number(s.conviction||0)>=(quality==="allocation"?56:60)&&evidence>=(quality==="allocation"?0.48:0.52);
|
return quality==="confirmed"&&edge>=MIN_ENTRY_EDGE;
|
||||||
return ["EV+","selective"].includes(quality)&&edge>=MIN_SELECTIVE_ENTRY_EDGE;
|
|
||||||
}
|
}
|
||||||
function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=null){
|
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,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 d=decision||{minConv:Math.max(cfg.minConv||0,cfg.aggressive?60:64),maxNew:Math.min(cfg.maxNew||4,cfg.aggressive?4:3),maxFrac:Math.min(cfg.maxFrac||0.05,cfg.aggressive?0.07:0.045),reserve:cfg.aggressive?0.10:0.18,targetExposure:cfg.targetExposure??(cfg.aggressive?0.75:0.62),minExposure:0};
|
||||||
const maxPositions=cfg.maxPositions||MAX_STRATEGY_POSITIONS;
|
const maxPositions=cfg.maxPositions||MAX_STRATEGY_POSITIONS;
|
||||||
const categoryCap=cfg.maxCategoryPct||MAX_CATEGORY_EXPOSURE_PCT;
|
const categoryCap=cfg.maxCategoryPct||MAX_CATEGORY_EXPOSURE_PCT;
|
||||||
const probeQualities=["trend","liquidity","core-probe","speculative","allocation","explore"];
|
const probeQualities=["trend","liquid-trend","reversal","catalyst"];
|
||||||
const eqBefore=equity(p);
|
const eqBefore=equity(p);
|
||||||
const positionValueBefore=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
|
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 currentExposure=eqBefore>0?positionValueBefore/eqBefore:0;
|
||||||
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=clamp(Number(d.minExposure??Math.max(0,targetExposure-0.16)),0,targetExposure);
|
const minExposure=0,belowFloor=false;
|
||||||
const belowFloor=currentExposure+0.001<minExposure;
|
|
||||||
const needsRefill=currentExposure+0.001<targetExposure;
|
|
||||||
const tradeReadyCount=rankedSugs.filter(s=>s.trade_ready).length;
|
const tradeReadyCount=rankedSugs.filter(s=>s.trade_ready).length;
|
||||||
const strategyCount=rankedSugs.filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)).length;
|
const strategyCount=rankedSugs.filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)).length;
|
||||||
if((p.positions||[]).length>=maxPositions||d.maxNew<=0){
|
if((p.positions||[]).length>=maxPositions||d.maxNew<=0){
|
||||||
@@ -1909,42 +1891,34 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
|
|||||||
}
|
}
|
||||||
const avoid=avoidMarketIds||new Set();
|
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")
|
const cands=rankedSugs.map(s=>peerAdjustedSuggestion(s,peerStats)).filter(s=>s.trade_ready&&agentAcceptsSuggestion(cfg,s)&&(s.side==="YES"||s.side==="NO")
|
||||||
&&(s.quality==="allocation"&&needsRefill
|
&&(cfg.aggressive?s.conviction>=d.minConv:s.peer_conviction>=d.minConv)
|
||||||
?(cfg.aggressive?s.conviction>=Math.min(d.minConv,50):s.peer_conviction>=Math.min(d.minConv,52))
|
&&s.conviction>=58&&s.entry_price>=0.08&&s.entry_price<=0.92
|
||||||
:(cfg.aggressive?s.conviction>=d.minConv:(s.quality==="core-probe"?s.conviction>=Math.min(d.minConv,60):s.peer_conviction>=d.minConv)))
|
&&effectiveEntryEdge(s)>=MIN_AGGRESSIVE_EDGE&&(s.days_to_resolution==null||s.days_to_resolution>=MIN_ENTRY_DAYS)
|
||||||
&&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.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)
|
&&(s.evidence_score==null||s.evidence_score>=0.46)
|
||||||
&&(!avoid.has(String(s.market_id))||(s.quality==="core-probe"&&!cfg.aggressive
|
&&(!avoid.has(String(s.market_id))||(Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<2
|
||||||
?Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).same||0)<4&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).coreSame||0)<1
|
&&Number((peerStats&&peerStats[`${s.market_id}:${s.side}`]||{}).opposite||0)===0))
|
||||||
: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))
|
&&!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)));
|
.sort((a,b)=>(b.peer_conviction-a.peer_conviction)||((b.peer_boost||0)-(a.peer_boost||0)));
|
||||||
let opened=0,openedCoreProbes=0,openedIds=[];
|
let opened=0,openedIds=[];
|
||||||
let cycleBudgetRemaining=eqBefore*(cfg.aggressive?0.22:0.16);
|
let cycleBudgetRemaining=eqBefore*(cfg.aggressive?0.14:0.10);
|
||||||
for(const s of cands){
|
for(const s of cands){
|
||||||
if(opened>=d.maxNew)break;
|
if(opened>=d.maxNew)break;
|
||||||
if((p.positions||[]).length>=maxPositions)break;
|
if((p.positions||[]).length>=maxPositions)break;
|
||||||
if(!cfg.aggressive&&s.quality==="core-probe"&&openedCoreProbes>=1)continue;
|
|
||||||
const eq=equity(p);
|
const eq=equity(p);
|
||||||
const positionValue=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
|
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 exposureGap=Math.max(0,eq*targetExposure-positionValue);
|
||||||
const investable=Math.min(p.cash-eq*d.reserve,exposureGap,cycleBudgetRemaining);
|
const investable=Math.min(p.cash-eq*d.reserve,exposureGap,cycleBudgetRemaining);
|
||||||
if(investable<=50)break;
|
if(investable<=50)break;
|
||||||
let frac;
|
let frac;
|
||||||
if(s.quality==="allocation"){frac=Math.min(d.maxFrac,cfg.allocationFrac??(cfg.aggressive?MAX_AGGRESSIVE_ALLOCATION_PCT:MAX_CORE_ALLOCATION_PCT));}
|
if(probeQualities.includes(s.quality)){frac=Math.min(d.maxFrac,cfg.aggressive?0.05:0.025);}
|
||||||
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 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);}
|
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)frac*=0.82;
|
||||||
if(s.peer_boost>0&&decision&&decision.urgency>0.65)frac*=1.08;
|
if(s.peer_boost>0&&decision&&decision.urgency>0.65)frac*=1.08;
|
||||||
let stake=Math.min(eq*frac,investable);
|
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);
|
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*recoveryCategoryCap-categoryValue));
|
stake=Math.min(stake,Math.max(0,eq*categoryCap-categoryValue));
|
||||||
if(stake<50)continue;
|
if(stake<50)continue;
|
||||||
const entry=s.entry_price; if(entry<=0||entry>=1)continue;
|
const entry=s.entry_price; if(entry<=0||entry>=1)continue;
|
||||||
const shares=+(stake/entry).toFixed(2),cost=+(shares*entry).toFixed(2);
|
const shares=+(stake/entry).toFixed(2),cost=+(shares*entry).toFixed(2);
|
||||||
@@ -1957,24 +1931,23 @@ function openPositions(p,cfg,rankedSugs,focus,decision,avoidMarketIds,peerStats=
|
|||||||
original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:s.conviction,peer_conviction:s.peer_conviction,category:s.category,opened_at:cycleIso(),url:s.url||"",
|
original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:s.conviction,peer_conviction:s.peer_conviction,category:s.category,opened_at:cycleIso(),url:s.url||"",
|
||||||
peer_note:s.peer_note||"",entry_reason:s.rationale||"",net_edge:s.net_edge,evidence_score:s.evidence_score,evidence_source_count:s.evidence_source_count||0,friction:s.friction,chase_penalty:s.chase_penalty,quality:s.quality,
|
peer_note:s.peer_note||"",entry_reason:s.rationale||"",net_edge:s.net_edge,evidence_score:s.evidence_score,evidence_source_count:s.evidence_source_count||0,friction:s.friction,chase_penalty:s.chase_penalty,quality:s.quality,
|
||||||
strategy_version:SUGGESTION_ENGINE_VERSION,
|
strategy_version:SUGGESTION_ENGINE_VERSION,
|
||||||
momentum_strength:s.momentum_strength,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,price_change_1d:s.price_change_1d,price_change_1w:s.price_change_1w,
|
||||||
gain_stops:{},stop_losses:{}});
|
peak_price:+entry.toFixed(4),gain_stops:{},stop_losses:{}});
|
||||||
p.history.push({date:logDay(),action:"OPEN",question:s.question,side:s.side,
|
p.history.push({date:logDay(),action:"OPEN",question:s.question,side:s.side,
|
||||||
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})`:""}`});
|
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));
|
opened++;openedIds.push(String(s.market_id));
|
||||||
}
|
}
|
||||||
const eqAfter=equity(p);
|
const eqAfter=equity(p);
|
||||||
const positionValueAfter=(p.positions||[]).reduce((sum,pos)=>sum+Number(pos.value||pos.shares*pos.current_price||0),0);
|
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;
|
const exposureAfter=eqAfter>0?positionValueAfter/eqAfter:0;
|
||||||
let allocationStatus;
|
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.`;
|
if(opened)allocationStatus=`Opened ${opened} confirmed position${opened===1?"":"s"}; exposure moved from ${Math.round(currentExposure*100)}% to ${Math.round(exposureAfter*100)}% under the ${Math.round(targetExposure*100)}% ceiling.`;
|
||||||
else if(exposureAfter+0.001>=targetExposure)allocationStatus=`Exposure reached its ${Math.round(targetExposure*100)}% target.`;
|
else if(exposureAfter+0.001>=targetExposure)allocationStatus=`The ${Math.round(targetExposure*100)}% exposure ceiling is full.`;
|
||||||
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 independently confirmed markets cleared the portfolio checks this cycle; cash was retained.";
|
||||||
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(!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 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.";
|
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:+minExposure.toFixed(3),belowFloor:exposureAfter+0.001<minExposure,needsRefill: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,allocationStatus});
|
tradeReadyCount,strategyCandidates:strategyCount,eligibleCandidates:cands.length,opened,allocationStatus});
|
||||||
return openedIds;
|
return openedIds;
|
||||||
}
|
}
|
||||||
@@ -2156,7 +2129,8 @@ function format24h(change){
|
|||||||
function board(){
|
function board(){
|
||||||
const st=loadState();
|
const st=loadState();
|
||||||
return AGENTS.map(c=>{const p=st.agents[c.id]||defaultPortfolio();const eq=equity(p);
|
return AGENTS.map(c=>{const p=st.agents[c.id]||defaultPortfolio();const eq=equity(p);
|
||||||
return {c,p,eq,pnl:eq-STARTING_BALANCE,ret:(eq/STARTING_BALANCE-1)*100,change24h:portfolioChange24h(p,eq)};})
|
const baseline=Number(p.engine_baseline&&p.engine_baseline.version===SUGGESTION_ENGINE_VERSION?p.engine_baseline.equity:eq);
|
||||||
|
return {c,p,eq,pnl:eq-STARTING_BALANCE,ret:(eq/STARTING_BALANCE-1)*100,enginePnl:eq-baseline,engineRet:baseline>0?(eq/baseline-1)*100:0,change24h:portfolioChange24h(p,eq)};})
|
||||||
.sort((a,b)=>b.eq-a.eq);
|
.sort((a,b)=>b.eq-a.eq);
|
||||||
}
|
}
|
||||||
const MEDALS=["🥇","🥈","🥉"];
|
const MEDALS=["🥇","🥈","🥉"];
|
||||||
@@ -2167,13 +2141,15 @@ function renderAll(){renderOverview();renderEmailAlerts();renderLeaderboard();re
|
|||||||
function renderOverview(){
|
function renderOverview(){
|
||||||
const b=board(); const lead=b[0];
|
const b=board(); const lead=b[0];
|
||||||
const avgRet=b.reduce((s,r)=>s+r.ret,0)/b.length;
|
const avgRet=b.reduce((s,r)=>s+r.ret,0)/b.length;
|
||||||
|
const engineAvg=b.reduce((s,r)=>s+r.engineRet,0)/b.length;
|
||||||
const core=b.filter(r=>!r.c.aggressive),coreAvg=core.reduce((s,r)=>s+r.ret,0)/Math.max(1,core.length);
|
const core=b.filter(r=>!r.c.aggressive),coreAvg=core.reduce((s,r)=>s+r.ret,0)/Math.max(1,core.length);
|
||||||
const aggressive=b.filter(r=>r.c.aggressive),aggressiveAvg=aggressive.reduce((s,r)=>s+r.ret,0)/Math.max(1,aggressive.length);
|
const aggressive=b.filter(r=>r.c.aggressive),aggressiveAvg=aggressive.reduce((s,r)=>s+r.ret,0)/Math.max(1,aggressive.length);
|
||||||
const sugCount=(loadSuggestions().suggestions||[]).length;
|
const sugCount=(loadSuggestions().suggestions||[]).length;
|
||||||
const stats=[
|
const stats=[
|
||||||
{ic:lead.c.emoji,label:"Leader",value:lead.c.name.split(" ")[0]},
|
{ic:lead.c.emoji,label:"Leader",value:lead.c.name.split(" ")[0]},
|
||||||
{ic:"📈",label:"Leader return",value:fmtPct(lead.ret),cls:signClass(lead.pnl)},
|
{ic:"📈",label:"Leader return",value:fmtPct(lead.ret),cls:signClass(lead.pnl)},
|
||||||
{ic:"⚖️",label:"Mixed-inception avg",value:fmtPct(avgRet),cls:signClass(avgRet)},
|
{ic:"⚖️",label:"Legacy avg",value:fmtPct(avgRet),cls:signClass(avgRet)},
|
||||||
|
{ic:"🧪",label:`v${SUGGESTION_ENGINE_VERSION} avg`,value:fmtPct(engineAvg),cls:signClass(engineAvg)},
|
||||||
{ic:"🧠",label:"Core strategy avg",value:fmtPct(coreAvg),cls:signClass(coreAvg)},
|
{ic:"🧠",label:"Core strategy avg",value:fmtPct(coreAvg),cls:signClass(coreAvg)},
|
||||||
{ic:"⚡",label:"Aggressive avg",value:fmtPct(aggressiveAvg),cls:signClass(aggressiveAvg)},
|
{ic:"⚡",label:"Aggressive avg",value:fmtPct(aggressiveAvg),cls:signClass(aggressiveAvg)},
|
||||||
{ic:"💡",label:"Scored ideas",value:sugCount},
|
{ic:"💡",label:"Scored ideas",value:sugCount},
|
||||||
@@ -2187,7 +2163,7 @@ function renderOverview(){
|
|||||||
}
|
}
|
||||||
function renderTopPicks(){
|
function renderTopPicks(){
|
||||||
const root=$("topPicks"); if(!root)return;
|
const root=$("topPicks"); if(!root)return;
|
||||||
const top=((loadSuggestions().suggestions)||[]).slice(0,5);
|
const top=((loadSuggestions().suggestions)||[]).filter(s=>s.trade_ready).slice(0,5);
|
||||||
if(!top.length){root.innerHTML=`<div class="empty">Run a cycle to see top picks.</div>`;return;}
|
if(!top.length){root.innerHTML=`<div class="empty">Run a cycle to see top picks.</div>`;return;}
|
||||||
root.innerHTML=top.map(s=>`<div class="pos"><div>
|
root.innerHTML=top.map(s=>`<div class="pos"><div>
|
||||||
<div class="pq">${s.url?`<a class="market-title" href="${esc(s.url)}" target="_blank" rel="noopener">${esc(s.question.slice(0,58))}${s.question.length>58?"…":""}</a>`:`${esc(s.question.slice(0,58))}${s.question.length>58?"…":""}`}</div>
|
<div class="pq">${s.url?`<a class="market-title" href="${esc(s.url)}" target="_blank" rel="noopener">${esc(s.question.slice(0,58))}${s.question.length>58?"…":""}</a>`:`${esc(s.question.slice(0,58))}${s.question.length>58?"…":""}`}</div>
|
||||||
@@ -2286,7 +2262,7 @@ function decisionSummary(p){
|
|||||||
const d=p.lastDecision;
|
const d=p.lastDecision;
|
||||||
if(!d)return "No live adaptation yet; this agent is still using its base rulebook.";
|
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)`:""}.`:"";
|
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)}%; operating band ${Math.round(Number(d.minExposure||0)*100)}-${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} 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}`;
|
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}`;
|
||||||
@@ -2296,7 +2272,7 @@ function renderAgentBrief(cfg,p,st){
|
|||||||
const bw=bestAndWorst(p);
|
const bw=bestAndWorst(p);
|
||||||
const risk=cfg.aggressive?"Extreme risk":cfg.maxFrac>=0.055?"Active":cfg.maxFrac<=0.04?"Broad active":"Disciplined";
|
const risk=cfg.aggressive?"Extreme risk":cfg.maxFrac>=0.055?"Active":cfg.maxFrac<=0.04?"Broad active":"Disciplined";
|
||||||
const cadence=`${cfg.maxNew||0} new trades max per cycle`;
|
const cadence=`${cfg.maxNew||0} new trades max per cycle`;
|
||||||
const exitRule=`Exit if edge fades, conviction weakens, <${EXIT_RESOLUTION_DAYS}d to resolve, or ${EXIT_STALE_DAYS}d stale`;
|
const exitRule=`Exit after a confirmed reversal, a 12h unconfirmed fade after 48h, a trailing-profit retrace, or ${EXIT_STALE_DAYS}d stale while losing`;
|
||||||
const thesis=agentPlainBlurb(cfg,st);
|
const thesis=agentPlainBlurb(cfg,st);
|
||||||
root.innerHTML=`
|
root.innerHTML=`
|
||||||
<div class="brief-main" style="border-top:3px solid ${cfg.color}">
|
<div class="brief-main" style="border-top:3px solid ${cfg.color}">
|
||||||
@@ -2307,7 +2283,7 @@ function renderAgentBrief(cfg,p,st){
|
|||||||
<div class="brief-mini"><div class="k">Style</div><div class="v">${esc(risk)}</div></div>
|
<div class="brief-mini"><div class="k">Style</div><div class="v">${esc(risk)}</div></div>
|
||||||
<div class="brief-mini"><div class="k">Trade Rule</div><div class="v">${esc(cadence)}</div></div>
|
<div class="brief-mini"><div class="k">Trade Rule</div><div class="v">${esc(cadence)}</div></div>
|
||||||
<div class="brief-mini"><div class="k">Stop Loss</div><div class="v">Sell all at -18%</div></div>
|
<div class="brief-mini"><div class="k">Stop Loss</div><div class="v">Sell all at -18%</div></div>
|
||||||
<div class="brief-mini"><div class="k">Gain Stop</div><div class="v">Sell 25% at +75%, +150%, +300%</div></div>
|
<div class="brief-mini"><div class="k">Profit Locks</div><div class="v">Three reachable, entry-aware 25% sales</div></div>
|
||||||
<div class="brief-mini"><div class="k">Policy Exit</div><div class="v">${esc(exitRule)}</div></div>
|
<div class="brief-mini"><div class="k">Policy Exit</div><div class="v">${esc(exitRule)}</div></div>
|
||||||
<div class="brief-mini"><div class="k">Best / Worst</div><div class="v"><span class="pos-val">${esc(bw.best)}</span><br><span class="neg-val">${esc(bw.worst)}</span></div></div>`;
|
<div class="brief-mini"><div class="k">Best / Worst</div><div class="v"><span class="pos-val">${esc(bw.best)}</span><br><span class="neg-val">${esc(bw.worst)}</span></div></div>`;
|
||||||
}
|
}
|
||||||
@@ -2319,7 +2295,7 @@ function renderLeaderboard(){
|
|||||||
<span class="rankpill">#${i+1}</span>
|
<span class="rankpill">#${i+1}</span>
|
||||||
<div class="lb-top"><span class="medal">${MEDALS[i]||""}</span><span class="lb-emoji">${r.c.emoji}</span>
|
<div class="lb-top"><span class="medal">${MEDALS[i]||""}</span><span class="lb-emoji">${r.c.emoji}</span>
|
||||||
<div><div class="lb-name">${r.c.name}</div><div class="lb-blurb">${agentBlurb(r.c,st)}</div></div></div>
|
<div><div class="lb-name">${r.c.name}</div><div class="lb-blurb">${agentBlurb(r.c,st)}</div></div></div>
|
||||||
<div class="lb-mid"><div class="lb-eq">${fmtUSD(r.eq)}</div><div><div class="lb-ret ${signClass(r.pnl)}">${fmtPct(r.ret)}</div><div class="small ${signClass(r.change24h.pct)}">24h ${format24h(r.change24h)}</div></div></div>
|
<div class="lb-mid"><div class="lb-eq">${fmtUSD(r.eq)}</div><div><div class="lb-ret ${signClass(r.pnl)}">${fmtPct(r.ret)}</div><div class="small ${signClass(r.enginePnl)}">v${SUGGESTION_ENGINE_VERSION} ${fmtPct(r.engineRet)}</div><div class="small ${signClass(r.change24h.pct)}">24h ${format24h(r.change24h)}</div></div></div>
|
||||||
<div class="lb-foot"><span class="muted small">${r.p.positions.length} open · ${r.p.closed.length} closed</span>${miniSpark(r.p.snapshots,r.c.color)}</div>
|
<div class="lb-foot"><span class="muted small">${r.p.positions.length} open · ${r.p.closed.length} closed</span>${miniSpark(r.p.snapshots,r.c.color)}</div>
|
||||||
</div>`).join("");
|
</div>`).join("");
|
||||||
document.querySelectorAll("#leaderboard .lb-card").forEach(el=>{
|
document.querySelectorAll("#leaderboard .lb-card").forEach(el=>{
|
||||||
@@ -2492,8 +2468,8 @@ 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 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, 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 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.`;
|
: `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=>{
|
||||||
const col=catColor(s.category);
|
const col=catColor(s.category);
|
||||||
@@ -2513,7 +2489,7 @@ function renderSuggestions(){
|
|||||||
<div class="rationale">${esc(s.rationale)}</div>
|
<div class="rationale">${esc(s.rationale)}</div>
|
||||||
<div class="drivers">${drivers}</div>
|
<div class="drivers">${drivers}</div>
|
||||||
<div class="metrics">
|
<div class="metrics">
|
||||||
<span>Net edge <b>${((Math.abs(s.net_edge!=null?s.net_edge:s.edge))*100).toFixed(1)}c</b></span><span>Evidence <b>${Math.round((s.evidence_score||0)*100)}</b></span>
|
<span>Signal margin <b>${((Math.abs(s.net_edge!=null?s.net_edge:s.edge))*100).toFixed(1)}c</b></span><span>Evidence <b>${Math.round((s.evidence_score||0)*100)}</b></span>
|
||||||
<span>Vol <b>${fmtUSD(s.volume)}</b></span><span>24h <b>${fmtUSD(s.volume_24hr)}</b></span><span>Resolves <b>${days}</b></span>
|
<span>Vol <b>${fmtUSD(s.volume)}</b></span><span>24h <b>${fmtUSD(s.volume_24hr)}</b></span><span>Resolves <b>${days}</b></span>
|
||||||
${s.url?`<a class="market-link" href="${esc(s.url)}" target="_blank" rel="noopener">Open on Polymarket ↗</a>`:""}
|
${s.url?`<a class="market-link" href="${esc(s.url)}" target="_blank" rel="noopener">Open on Polymarket ↗</a>`:""}
|
||||||
${PERSONAL_MODE?`<button class="btn ghost" data-stage-suggestion="${esc(s.market_id)}">Stage ticket</button>`:""}
|
${PERSONAL_MODE?`<button class="btn ghost" data-stage-suggestion="${esc(s.market_id)}">Stage ticket</button>`:""}
|
||||||
@@ -2532,6 +2508,7 @@ function renderPortfolioTab(){
|
|||||||
document.querySelectorAll("#agentSel .segbtn").forEach(el=>el.addEventListener("click",()=>{localStorage.setItem(VIEW_KEY,el.dataset.agent);renderPortfolioTab();}));
|
document.querySelectorAll("#agentSel .segbtn").forEach(el=>el.addEventListener("click",()=>{localStorage.setItem(VIEW_KEY,el.dataset.agent);renderPortfolioTab();}));
|
||||||
const p=st.agents[viewId]||defaultPortfolio();const eq=equity(p),pnl=eq-p.starting_balance;
|
const p=st.agents[viewId]||defaultPortfolio();const eq=equity(p),pnl=eq-p.starting_balance;
|
||||||
const change24h=portfolioChange24h(p,eq);
|
const change24h=portfolioChange24h(p,eq);
|
||||||
|
const engineBase=Number(p.engine_baseline&&p.engine_baseline.version===SUGGESTION_ENGINE_VERSION?p.engine_baseline.equity:eq),enginePnl=eq-engineBase;
|
||||||
renderAgentBrief(cfg,p,st);
|
renderAgentBrief(cfg,p,st);
|
||||||
const stats=[
|
const stats=[
|
||||||
{ic:"💰",label:"Equity",value:fmtUSD(eq)},
|
{ic:"💰",label:"Equity",value:fmtUSD(eq)},
|
||||||
@@ -2539,6 +2516,7 @@ function renderPortfolioTab(){
|
|||||||
{ic:"📈",label:"P&L",value:fmtUSD(pnl),cls:signClass(pnl)},
|
{ic:"📈",label:"P&L",value:fmtUSD(pnl),cls:signClass(pnl)},
|
||||||
{ic:"🎯",label:"Return",value:fmtPct((eq/p.starting_balance-1)*100),cls:signClass(pnl)},
|
{ic:"🎯",label:"Return",value:fmtPct((eq/p.starting_balance-1)*100),cls:signClass(pnl)},
|
||||||
{ic:"🕒",label:"24h change",value:format24h(change24h),cls:signClass(change24h.pct)},
|
{ic:"🕒",label:"24h change",value:format24h(change24h),cls:signClass(change24h.pct)},
|
||||||
|
{ic:"🧪",label:`v${SUGGESTION_ENGINE_VERSION}`,value:fmtPct(engineBase>0?(eq/engineBase-1)*100:0),cls:signClass(enginePnl)},
|
||||||
{ic:"📂",label:"Open",value:p.positions.length},
|
{ic:"📂",label:"Open",value:p.positions.length},
|
||||||
];
|
];
|
||||||
$("statsPf").innerHTML=stats.map(s=>`<div class="stat"><div class="ic">${s.ic}</div><div class="label">${s.label}</div><div class="value ${s.cls||""}">${s.value}</div></div>`).join("");
|
$("statsPf").innerHTML=stats.map(s=>`<div class="stat"><div class="ic">${s.ic}</div><div class="label">${s.label}</div><div class="value ${s.cls||""}">${s.value}</div></div>`).join("");
|
||||||
@@ -2591,14 +2569,14 @@ function renderAgentTechnical(agentId){
|
|||||||
}
|
}
|
||||||
function agentCompetitionPlan(cfg,row,rank,leader){
|
function agentCompetitionPlan(cfg,row,rank,leader){
|
||||||
if(rank===1)return "Plan: press the lead through the strongest qualifying trades while keeping stop-loss and exposure limits active.";
|
if(rank===1)return "Plan: press the lead through the strongest qualifying trades while keeping stop-loss and exposure limits active.";
|
||||||
if(cfg.id==="value")return "Plan: close the gap by waiting for the cleanest model-versus-market mispricings and avoiding crowded trades without enough edge.";
|
if(cfg.id==="value")return "Plan: close the gap by waiting for the cleanest confirmed movement after friction and avoiding crowded trades without enough signal margin.";
|
||||||
if(cfg.id==="momentum")return "Plan: attack fast-moving markets where fresh volume confirms attention, hoping speed beats slower value strategies.";
|
if(cfg.id==="momentum")return "Plan: attack fast-moving markets where fresh volume confirms attention, hoping speed beats slower value strategies.";
|
||||||
if(cfg.id==="favorite")return "Plan: grind upward through high-probability favorites, aiming to outlast more volatile agents during choppy markets.";
|
if(cfg.id==="favorite")return "Plan: grind upward through high-probability favorites, aiming to outlast more volatile agents during choppy markets.";
|
||||||
if(cfg.id==="longshot")return "Plan: keep risk small but search for one underpriced outsider that can reprice sharply and leapfrog the leaderboard.";
|
if(cfg.id==="longshot")return "Plan: keep risk small but search for one underpriced outsider that can reprice sharply and leapfrog the leaderboard.";
|
||||||
if(cfg.id==="diversifier")return "Plan: spread bets broadly, reduce single-market damage, and try to win through consistency rather than one heroic call.";
|
if(cfg.id==="diversifier")return "Plan: spread bets broadly, reduce single-market damage, and try to win through consistency rather than one heroic call.";
|
||||||
if(cfg.id==="catalyst")return "Plan: deploy quickly when fresh real-world context and measured edge align, then concentrate enough capital for a catalyst repricing to move the account.";
|
if(cfg.id==="catalyst")return "Plan: deploy quickly when fresh real-world context and measured edge align, then concentrate enough capital for a catalyst repricing to move the account.";
|
||||||
if(cfg.id==="reversal")return "Plan: attack overextended one-day moves from the opposite side, looking for liquid mean-reversion setups with enough evidence to justify the contrarian risk.";
|
if(cfg.id==="reversal")return "Plan: attack overextended one-day moves from the opposite side, looking for liquid mean-reversion setups with enough evidence to justify the contrarian risk.";
|
||||||
if(cfg.id==="breakout")return "Plan: stay nearly fully deployed in confirmed momentum setups and press the fastest breakouts where direction, activity, and edge agree.";
|
if(cfg.id==="breakout")return "Plan: enter only confirmed momentum setups and press the cleanest breakouts where direction, activity, and signal margin agree.";
|
||||||
if(cfg.id==="tailalpha")return "Plan: hunt underpriced low-cost contracts with asymmetric upside, accepting a volatile hit rate while keeping each tail position survivable.";
|
if(cfg.id==="tailalpha")return "Plan: hunt underpriced low-cost contracts with asymmetric upside, accepting a volatile hit rate while keeping each tail position survivable.";
|
||||||
if(cfg.id==="conviction")return "Plan: ignore marginal trades and commit the largest allowed stakes to a compact book of the strongest edge-and-evidence combinations.";
|
if(cfg.id==="conviction")return "Plan: ignore marginal trades and commit the largest allowed stakes to a compact book of the strongest edge-and-evidence combinations.";
|
||||||
return "Plan: keep following its rulebook and use fresh market data to pressure the leaderboard.";
|
return "Plan: keep following its rulebook and use fresh market data to pressure the leaderboard.";
|
||||||
@@ -2850,7 +2828,7 @@ function agentChatReply(agentId,question,history=[]){
|
|||||||
return `My style is ${agentVoice(cfg).tone}.\n\n${agentPlainBlurb(cfg,st)}\n\nCurrent adaptation: ${decisionSummary(p)}`;
|
return `My style is ${agentVoice(cfg).tone}.\n\n${agentPlainBlurb(cfg,st)}\n\nCurrent adaptation: ${decisionSummary(p)}`;
|
||||||
}
|
}
|
||||||
if(/risk|stop|loss|gain|take profit|sell/.test(combined)){
|
if(/risk|stop|loss|gain|take profit|sell/.test(combined)){
|
||||||
return `My risk rules are mechanical:\n\nStop loss: sell the full remaining position at -18% from entry.\nGain stop: sell 25% at +75%, another 25% at +150%, and another 25% at +300%.\n\nRecent risk behavior: ${recentActionSummary(p)}.`;
|
return `My risk rules are mechanical:\n\nStop loss: sell the full remaining position at -18% from entry.\nProfit locks: sell 25% at three entry-aware targets that remain reachable below $1, then protect gains with a trailing exit. Signal exits need at least 24 hours of holding and six hours of confirmed conflict.\n\nRecent risk behavior: ${recentActionSummary(p)}.`;
|
||||||
}
|
}
|
||||||
if(/mistake|wrong|regret|fix|improve|better/.test(combined)){
|
if(/mistake|wrong|regret|fix|improve|better/.test(combined)){
|
||||||
return `${regretNarrative(row,p)}\n\nThe fix is not “bet harder.” The fix is stricter entries, fewer positions, and cutting trades when the model edge fades. That is the discipline I am trying to follow now.`;
|
return `${regretNarrative(row,p)}\n\nThe fix is not “bet harder.” The fix is stricter entries, fewer positions, and cutting trades when the model edge fades. That is the discipline I am trying to follow now.`;
|
||||||
@@ -3839,6 +3817,46 @@ function showTab(name){
|
|||||||
document.querySelectorAll(".tab").forEach(t=>t.addEventListener("click",()=>showTab(t.dataset.tab)));
|
document.querySelectorAll(".tab").forEach(t=>t.addEventListener("click",()=>showTab(t.dataset.tab)));
|
||||||
window.addEventListener("hashchange",()=>showTab(location.hash.slice(1)));
|
window.addEventListener("hashchange",()=>showTab(location.hash.slice(1)));
|
||||||
|
|
||||||
|
window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
|
||||||
|
version:SUGGESTION_ENGINE_VERSION,
|
||||||
|
analyzeMarket,
|
||||||
|
gainStopTargets:(entry)=>GAIN_STOP_TIERS.map(t=>gainStopTarget({entry_price:Number(entry),cost:1,shares:1,gain_stops:{}},t)),
|
||||||
|
rules:Object.freeze({minimumPolicyHoldHours:MIN_POLICY_HOLD_HOURS,exitConfirmationHours:EXIT_CONFIRM_HOURS,maxAgentOverlap:2,stopLossPct:18}),
|
||||||
|
});
|
||||||
|
function runEngineSelfTest(){
|
||||||
|
const market=(overrides={})=>Object.assign({
|
||||||
|
id:"self-test",question:"Will the president win the election?",event:"Election",category:"Politics",tags:["election"],
|
||||||
|
yes_price:0.42,no_price:0.58,volume:500000,volume_24hr:60000,volume_1wk:280000,liquidity:75000,spread:0.01,
|
||||||
|
price_change_1h:0.004,price_change_1d:0.035,price_change_1w:0.08,days_to_resolution:30,
|
||||||
|
clob_token_ids:["yes","no"],url:"",closed:false,accepting_orders:true,
|
||||||
|
},overrides);
|
||||||
|
const news={"self-test":{source_count:3,latest_title:"Election polling update"}};
|
||||||
|
const trend=analyzeMarket(market(),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 targets=window.PMA_ENGINE_DIAGNOSTICS.gainStopTargets(0.82);
|
||||||
|
const hoursAgo=h=>new Date(Date.now()-h*3600000).toISOString();
|
||||||
|
const conflict={trade_ready:true,side:"NO",net_edge:-0.03,conviction:72};
|
||||||
|
const mature={entry_price:0.42,current_price:0.39,opened_at:hoursAgo(48),signal_conflict_since:hoursAgo(7),unrealized_pnl:-10,quality:"confirmed"};
|
||||||
|
const young={entry_price:0.42,current_price:0.39,opened_at:hoursAgo(2),signal_conflict_since:hoursAgo(1),unrealized_pnl:-10,quality:"confirmed"};
|
||||||
|
const trailing={entry_price:0.40,current_price:0.45,peak_price:0.55,opened_at:hoursAgo(48),unrealized_pnl:10,quality:"confirmed"};
|
||||||
|
const fresh=market();
|
||||||
|
const mock={agents:{}};AGENTS.forEach(a=>mock.agents[a.id]=defaultPortfolio());
|
||||||
|
["value","momentum","favorite"].forEach((id,i)=>mock.agents[id].positions.push({market_id:"overlap-test",question:"Overlap test",side:"YES",shares:100,current_price:0.5,entry_price:0.5,cost:50,value:50,unrealized_pnl:0,conviction:70-i,opened_at:hoursAgo(24)}));
|
||||||
|
reduceStrategyOverlap(mock);
|
||||||
|
const overlapRemaining=AGENTS.reduce((sum,a)=>sum+mock.agents[a.id].positions.filter(p=>p.market_id==="overlap-test").length,0);
|
||||||
|
return {version:SUGGESTION_ENGINE_VERSION,
|
||||||
|
trend:{ready:trend.trade_ready,quality:trend.quality,side:trend.side,margin:trend.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},
|
||||||
|
highEntryTargets:targets,targetsReachable:targets.every(x=>x>0.82&&x<1),
|
||||||
|
exits:{youngConflict:exitReason(young,fresh,conflict,AGENTS[0]),matureConflict:exitReason(mature,fresh,conflict,AGENTS[0]),trailing:trailingProfitReason(trailing)},
|
||||||
|
overlapRemaining,rules:window.PMA_ENGINE_DIAGNOSTICS.rules};
|
||||||
|
}
|
||||||
|
if(new URLSearchParams(location.search).get("engine_test")==="1"){
|
||||||
|
const output=document.createElement("output");output.id="engineSelfTest";output.hidden=true;output.textContent=JSON.stringify(runEngineSelfTest());document.body.appendChild(output);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Wire up ---------- */
|
/* ---------- Wire up ---------- */
|
||||||
applyPersonalMode();
|
applyPersonalMode();
|
||||||
initProviderConfig();
|
initProviderConfig();
|
||||||
@@ -3969,7 +3987,7 @@ async function autoRunDueCycle(){
|
|||||||
}
|
}
|
||||||
setInterval(()=>autoRunDueCycle(),RUN_INTERVAL_MS);
|
setInterval(()=>autoRunDueCycle(),RUN_INTERVAL_MS);
|
||||||
|
|
||||||
/* On load: first visit runs a 7-day backtest; afterwards the shared portfolio advances as often as the cycle key allows. */
|
/* On load: a new shared portfolio starts with an honest zero baseline; later cycles use live market snapshots. */
|
||||||
(async function init(){
|
(async function init(){
|
||||||
showTab(location.hash.slice(1)||"overview");
|
showTab(location.hash.slice(1)||"overview");
|
||||||
fetchLiveBackendStatus();
|
fetchLiveBackendStatus();
|
||||||
@@ -3989,10 +4007,14 @@ setInterval(()=>autoRunDueCycle(),RUN_INTERVAL_MS);
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!st.seeded){
|
if(!st.seeded){
|
||||||
btn.disabled=true;btn.textContent="Backtesting…";
|
btn.disabled=true;btn.textContent="Starting…";
|
||||||
try{await backtestWeek(st);st.date=todayStr();st.last_run=nowIso();st.last_cycle_hour=currentCycleHour();saveState(st);if(!(await pushCloudState()))throw new Error("Cloud sync unavailable");renderAll();
|
try{
|
||||||
toast("7-day backtest complete — live tracking begins today.");}
|
SNAP_TS=nowIso();AGENTS.forEach(a=>recordSnapshot(st.agents[a.id]));SNAP_TS=null;
|
||||||
catch(e){setStatus("backtest error — click Run",false);}
|
st.seeded=true;st.date=todayStr();st.last_run=nowIso();st.last_cycle_hour=null;saveState(st);
|
||||||
|
if(!(await pushCloudState()))throw new Error("Cloud sync unavailable");
|
||||||
|
await runDailyCycle();renderAll();toast("Live paper tracking started from a clean zero baseline.");
|
||||||
|
}
|
||||||
|
catch(e){setStatus("start error — click Run",false);}
|
||||||
btn.disabled=false;btn.textContent="Run cycle";
|
btn.disabled=false;btn.textContent="Run cycle";
|
||||||
}else if(st.last_cycle_hour!==currentCycleHour()){
|
}else if(st.last_cycle_hour!==currentCycleHour()){
|
||||||
btn.disabled=true;btn.textContent="Running…";
|
btn.disabled=true;btn.textContent="Running…";
|
||||||
|
|||||||
Reference in New Issue
Block a user