@@ -739,6 +759,7 @@ const CHART_RANGE_KEY = "pma_chart_range_v1";
const INVEST_KEY = "pma_invest_allocations_v1";
const PAPER_KEY = "pma_paper_accounts_v1";
const LIVE_KEY = "pma_live_readiness_v1";
+const CHAT_KEY = "pma_agent_chat_v1";
const PAPER_SESSION_PREFIX = "pma_cloud_session_";
const PERSONAL_MODE = new URLSearchParams(location.search).get("personal") === "1";
const PERSONAL_USER_ID = "local-readiness-user";
@@ -1957,6 +1978,7 @@ function renderPortfolioTab(){
renderHistory((p.history||[]).slice(-50).reverse());
renderAgentTechnical(viewId);
renderAgentReports();
+ renderAgentChat(viewId);
}
const avg=(arr)=>arr.length?arr.reduce((s,x)=>s+x,0)/arr.length:0;
function ema(vals,period){
@@ -2109,6 +2131,92 @@ function renderAgentReports(){
`;
}).join("");
}
+function loadAgentChats(){try{return JSON.parse(localStorage.getItem(CHAT_KEY)||"{}");}catch(e){return {};}}
+function saveAgentChats(chats){localStorage.setItem(CHAT_KEY,JSON.stringify(chats));}
+function agentChatSeed(agentId){
+ const cfg=agentById(agentId),st=loadState(),p=st.agents[agentId]||defaultPortfolio(),eq=equity(p),ret=(eq/p.starting_balance-1)*100;
+ return [{role:"agent",text:`I'm ${cfg.name}. Ask me why I am up or down, what trades are helping or hurting, what my strategy is, or how I plan to compete from here. Current return: ${fmtPct(ret)}.`}];
+}
+function topPositionText(p,n=3){
+ const open=(p.positions||[]).slice().sort((a,b)=>Math.abs(b.unrealized_pnl||0)-Math.abs(a.unrealized_pnl||0)).slice(0,n);
+ if(!open.length)return "I do not have open positions yet.";
+ return open.map((pos,i)=>`${i+1}. ${shortQuestion(pos.question,58)} — ${pos.side}, value ${fmtUSD(pos.value||0)}, P&L ${(pos.unrealized_pnl||0)>=0?"+":""}${fmtUSD(pos.unrealized_pnl||0)}`).join("\n");
+}
+function relevantSuggestionText(cfg,n=3){
+ const sugs=(loadSuggestions().suggestions||[]);
+ const ranked=cfg.rank?safeRankSuggestions(cfg,sugs):sugs;
+ const picks=ranked.filter(s=>s.side==="YES"||s.side==="NO").slice(0,n);
+ if(!picks.length)return "I need a fresh cycle before I can point to new suggestions.";
+ return picks.map((s,i)=>`${i+1}. ${shortQuestion(s.question,58)} — BUY ${s.side} @ ${Math.round(s.entry_price*100)}c, conviction ${Math.round(s.conviction)}`).join("\n");
+}
+function safeRankSuggestions(cfg,sugs){
+ try{return cfg.rank(sugs);}catch(e){return sugs;}
+}
+function agentChatReply(agentId,question){
+ const st=loadState(),cfg=agentById(agentId),p=st.agents[agentId]||defaultPortfolio();
+ const rows=board(),row=rows.find(r=>r.c.id===agentId)||{c:cfg,p,eq:equity(p),pnl:equity(p)-p.starting_balance,ret:(equity(p)/p.starting_balance-1)*100};
+ const rank=rows.findIndex(r=>r.c.id===agentId)+1||rows.length,leader=rows[0];
+ const q=String(question||"").toLowerCase();
+ const perf=dailyPerformanceExplanation(row);
+ if(/why|perform|performance|up|down|bad|poor|well|crash|gain|loss|today|day/.test(q)){
+ return `Here is my honest performance read:\n\n${perf}\n\nMy current rank is #${rank}, with ${fmtUSD(row.eq)} equity and ${fmtPct(row.ret)} return.`;
+ }
+ if(/position|trade|holding|holdings|bet|market/.test(q)){
+ return `These are the positions currently driving me most:\n\n${topPositionText(p)}\n\n${reportPositionLine(p)}`;
+ }
+ if(/strategy|style|how do you trade|rule|approach/.test(q)){
+ return `My strategy: ${agentPlainBlurb(cfg,st)}\n\nCurrent adaptation: ${decisionSummary(p)}`;
+ }
+ if(/risk|stop|loss|gain|take profit|sell/.test(q)){
+ return `My risk rules are mechanical:\n\nStop loss: sell 33% at -15%, another 33% at -30%, and all remaining at -45%.\nGain stop: sell 25% at +75%, another 25% at +150%, and another 25% at +300%.\n\nRecent risk behavior: ${recentActionSummary(p)}.`;
+ }
+ if(/plan|next|compete|leader|rank|catch/.test(q)){
+ return `${agentCompetitionPlan(cfg,row,rank,leader)}\n\nI am #${rank}. ${leader&&leader.c.id!==cfg.id?`${leader.c.name} leads at ${fmtUSD(leader.eq)}, so I am trailing by ${fmtUSD(leader.eq-row.eq)}.`:"I am currently defending the lead."}\n\nMy best near-term ideas from the current suggestion pool:\n${relevantSuggestionText(cfg)}`;
+ }
+ if(/suggest|idea|entry|buy/.test(q)){
+ return `The freshest ideas that fit my style are:\n\n${relevantSuggestionText(cfg)}\n\nI still need the normal cycle rules to decide whether sizing, overlap, cash reserve, and conviction are acceptable.`;
+ }
+ if(/compare|other agent|better|best/.test(q)){
+ const lines=rows.slice(0,5).map((r,i)=>`${i+1}. ${r.c.name}: ${fmtPct(r.ret)} (${fmtUSD(r.eq)})`).join("\n");
+ return `Top of the leaderboard right now:\n\n${lines}\n\nMy place: #${rank}. My edge is not just rank; it is whether my style is working in the current market regime. ${perf}`;
+ }
+ return `Short version: I am ${cfg.name}, currently #${rank} with ${fmtUSD(row.eq)} equity and ${fmtPct(row.ret)} return.\n\n${perf}\n\nAsk me about my positions, strategy, risk rules, next plan, or how I compare with the other agents.`;
+}
+function renderAgentChat(agentId){
+ const root=$("agentChatRoot");if(!root)return;
+ const cfg=agentById(agentId),st=loadState(),p=st.agents[agentId]||defaultPortfolio(),eq=equity(p),ret=(eq/p.starting_balance-1)*100;
+ const chats=loadAgentChats();
+ if(!chats[agentId]){chats[agentId]=agentChatSeed(agentId);saveAgentChats(chats);}
+ const msgs=chats[agentId].slice(-30);
+ root.innerHTML=`
+
+
${cfg.emoji}
${esc(cfg.name)}
${fmtUSD(eq)} equity · ${fmtPct(ret)} return · ${p.positions.length} open