An official close mark appears on the entry day. Complete session counts and the cumulative chart advance only when a new open-to-open interval is observable.
The decision session is not a holding session. A July 29 close signal enters at the July 30 open. Its July 30 close can provide an entry-day mark, but the first complete open-to-open session is available at the July 31 open. Entry-day marks are never mixed with validation estimates or final horizon outcomes.
+
Prospective strategy evidence
An official close mark appears on the entry day. It is shown as one observed partial day; the completed open-to-open counter and cumulative chart advance at the next open.
The decision session is not a holding session. A July 29 close signal enters at the July 30 open. At the July 30 close, the dashboard reports 1/5 observed (partial) and the official entry-day mark, while retaining 0 complete open-to-open sessions. The first complete interval is available at the July 31 open. Entry-day marks are never mixed with validation estimates or final horizon outcomes.
Horizon comparison
Paired Ridge and XGBoost development evidence with each arm’s own purge, feature subset, refit interval, and maturity date.
Selected model specifications
The winning development configuration for each visible arm. Open a model’s explainability panel to inspect genuine SHAP importance when that frozen vintage captured it.
Pipeline and risk controls
Freshness, universe, NLP, tactical routing, survival guardrails, and artifact integrity for the selected decision date.
@@ -46,20 +46,20 @@
const data=JSON.parse(document.getElementById('dashboard-data').textContent),decisionSelect=document.getElementById('decisionSelect'),familyFilter=document.getElementById('familyFilter'),horizonFilter=document.getElementById('horizonFilter'),STATE_KEY='live-forward-horizon-dashboard-v1';
const COLORS={ridge:'#3156d3',xgb_ranker:'#0f9f8f',ridge_h5:'#7290eb',ridge_h10:'#4f70dc',ridge_h20:'#3156d3',xgb_ranker_h5:'#70c8bd',xgb_ranker_h10:'#3eb2a4',xgb_ranker_h20:'#0f9f8f',spy:'#d89614'},names={ridge:'Ridge',xgb_ranker:'XGBoost'},fmtPct=v=>v==null||!Number.isFinite(Number(v))?'n/a':(Number(v)*100).toFixed(2)+'%',fmtNum=(v,d=3)=>v==null||!Number.isFinite(Number(v))?'n/a':Number(v).toFixed(d),human=v=>String(v??'').replaceAll('_',' ').replace(/\b\w/g,c=>c.toUpperCase()),tone=v=>Number(v)>=0?'pos':'neg',armColor=a=>COLORS[`${a.family}_h${a.horizon_sessions}`]||COLORS[a.family],armLabel=a=>`${names[a.family]||human(a.family)} · ${a.horizon_sessions} sessions`;
let saved={};try{saved=JSON.parse(localStorage.getItem(STATE_KEY)||'{}')}catch{}let hiddenSeries=new Set(saved.hiddenSeries||[]);
-function save(){try{localStorage.setItem(STATE_KEY,JSON.stringify({decisionDate:decisionSelect.value,family:familyFilter.value,horizon:horizonFilter.value,hiddenSeries:[...hiddenSeries]}))}catch{}}
+function save(){try{localStorage.setItem(STATE_KEY,JSON.stringify({decisionDate:decisionSelect.value,dataLatestDate:dates[0]||null,family:familyFilter.value,horizon:horizonFilter.value,hiddenSeries:[...hiddenSeries]}))}catch{}}
function option(value,label){const node=document.createElement('option');node.value=value;node.textContent=label;return node}
-const dates=[...new Set(data.arms.map(a=>a.decision_date))].sort().reverse();dates.forEach(d=>decisionSelect.appendChild(option(d,d)));decisionSelect.value=dates.includes(saved.decisionDate)?saved.decisionDate:dates[0]||'';familyFilter.value=['ridge','xgb_ranker'].includes(saved.family)?saved.family:'all';horizonFilter.value=['5','10','20'].includes(saved.horizon)?saved.horizon:'all';
+const dates=[...new Set(data.arms.map(a=>a.decision_date))].sort().reverse();dates.forEach(d=>decisionSelect.appendChild(option(d,d)));const savedDateMatchesCurrentDataset=saved.dataLatestDate===dates[0];decisionSelect.value=savedDateMatchesCurrentDataset&&dates.includes(saved.decisionDate)?saved.decisionDate:dates[0]||'';familyFilter.value=['ridge','xgb_ranker'].includes(saved.family)?saved.family:'all';horizonFilter.value=['5','10','20'].includes(saved.horizon)?saved.horizon:'all';
function decisionArms(){return data.arms.filter(a=>a.decision_date===decisionSelect.value)}
function visibleArms(){return decisionArms().filter(a=>(familyFilter.value==='all'||a.family===familyFilter.value)&&(horizonFilter.value==='all'||String(a.horizon_sessions)===horizonFilter.value))}
function table(rows,columns,sortState){if(!rows.length)return'
No matching recordsAdjust the filters or wait for the required outcome window.
`}
function renderNotice(){const arms=decisionArms(),pending=arms.filter(a=>a.status==='pending').length,next=[...new Set(arms.map(a=>a.expected_entry_date).filter(Boolean))].sort()[0],policy=data.rebalance_policy||{};document.getElementById('noticeTitle').textContent=`${pending} frozen forecasts ready for the next session`;document.getElementById('noticeCopy').textContent=`Decision ${decisionSelect.value} · expected entry ${next||'pending'} · partial marks update session by session; final labels wait for full maturity.`;document.getElementById('policyBadge').textContent=`${human(policy.status||'under audit')} · buffer ${policy.buffer_enabled?'on':'off'}`;document.getElementById('heroDate').textContent=`Latest decision · ${dates[0]||'n/a'}`}
function renderKpis(){const arms=visibleArms(),matured=data.matured.filter(x=>!decisionSelect.value||x.decision_date<=decisionSelect.value),audit=data.input_audit||{},universe=data.universe||{},items=[['Active arms',arms.length,'#3156d3',`${decisionArms().length} total for this decision`],['Matured forecasts',matured.length,'#7559c7',matured.length?'Prospective outcomes available':'First outcomes still pending'],['Eligible universe',universe.final_names,'#0f9f8f',`${universe.current_liquid_names??0} liquid names`],['Sentiment coverage',arms[0]?.sentiment_coverage,'#d89614',`${audit.processed_news_rows??0} recent articles processed`]];document.getElementById('kpis').innerHTML=items.map(([label,value,color,detail],i)=>`${label}${i===3?fmtPct(value):value??'n/a'}${detail}`).join('');document.getElementById('dataStatus').textContent=`${dates.length} decision date${dates.length===1?'':'s'} · ${data.arms.length} frozen forecasts`}
function strategyGroups(){const by=new Map,spyByDate=new Map;data.strategy_returns.forEach(row=>{const id=`${row.family}_h${row.horizon_sessions}`;if(!by.has(id))by.set(id,[]);by.get(id).push(row);if(Number.isFinite(Number(row.spy_return))&&!spyByDate.has(row.return_date))spyByDate.set(row.return_date,{return_date:row.return_date,gross_return:Number(row.spy_return)})});if(spyByDate.size)by.set('spy',[...spyByDate.values()]);return [...by.entries()].map(([id,rows])=>({id,rows:rows.sort((a,b)=>String(a.return_date).localeCompare(String(b.return_date)))}))}
-function renderStrategy(){const arms=visibleArms(),visibleIds=new Set([...arms.map(a=>`${a.family}_h${a.horizon_sessions}`),'spy']),groups=strategyGroups().filter(g=>visibleIds.has(g.id)),legend=document.getElementById('strategyLegend');document.getElementById('strategyProgress').innerHTML=arms.map(a=>{const rows=data.strategy_returns.filter(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date).sort((x,y)=>String(x.return_date).localeCompare(String(y.return_date))),mark=(data.strategy_interim_marks||[]).find(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date),observed=rows.length,cumulative=rows.reduce((value,row)=>value*(1+Number(row.gross_return)),1)-1,spyCumulative=rows.reduce((value,row)=>Number.isFinite(Number(row.spy_return))?value*(1+Number(row.spy_return)):value,1)-1,asOf=data.experiment.decision_date,status=observed?`${fmtPct(cumulative)} interim · ${fmtPct(cumulative-spyCumulative)} vs SPY`:mark?`Entry-day close mark ${fmtPct(mark.gross_mark_to_close)} · ${fmtPct(mark.active_mark_vs_spy)} vs SPY`:(asOf
${status} · complete label ${a.expected_maturity_date}`}).join('');legend.innerHTML=groups.map(g=>``).join('');legend.querySelectorAll('[data-series]').forEach(b=>b.addEventListener('click',()=>{hiddenSeries.has(b.dataset.series)?hiddenSeries.delete(b.dataset.series):hiddenSeries.add(b.dataset.series);renderStrategy();save()}));const visible=groups.filter(g=>!hiddenSeries.has(g.id));if(!visible.length){const entries=arms.map(a=>a.expected_entry_date).filter(Boolean).sort(),marks=arms.map(a=>(data.strategy_interim_marks||[]).find(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date)).filter(Boolean),message=marks.length?'Official entry-day close marks are shown above. The cumulative chart begins after the first complete open-to-open session.':`The ${decisionSelect.value} close decision first becomes invested at the ${entries[0]||'next configured'} open.`;document.getElementById('strategyChart').innerHTML=`
No complete open-to-open session yet${message}
`;return}const lines=visible.map(g=>{let cumulative=1;return{id:g.id,points:g.rows.map(r=>({date:r.return_date,value:(cumulative*=1+Number(r.gross_return))-1}))}}),stamps=[...new Set(lines.flatMap(l=>l.points.map(p=>p.date)))].sort(),vals=lines.flatMap(l=>l.points.map(p=>p.value)),min=Math.min(0,...vals),max=Math.max(0,...vals),pad=Math.max((max-min)*.14,.001),w=1180,h=360,left=66,right=22,top=18,bottom=48,pw=w-left-right,ph=h-top-bottom,x=s=>left+stamps.indexOf(s)/Math.max(1,stamps.length-1)*pw,y=v=>top+(1-(v-(min-pad))/((max+pad)-(min-pad)||1))*ph,ticks=[0,.25,.5,.75,1].map(f=>max+pad-f*((max+pad)-(min-pad))),grid=ticks.map(v=>`${fmtPct(v)}`).join(''),paths=lines.map(line=>``).join('');document.getElementById('strategyChart').innerHTML=``}
+function renderStrategy(){const arms=visibleArms(),visibleIds=new Set([...arms.map(a=>`${a.family}_h${a.horizon_sessions}`),'spy']),groups=strategyGroups().filter(g=>visibleIds.has(g.id)),legend=document.getElementById('strategyLegend');document.getElementById('strategyProgress').innerHTML=arms.map(a=>{const rows=data.strategy_returns.filter(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date).sort((x,y)=>String(x.return_date).localeCompare(String(y.return_date))),mark=(data.strategy_interim_marks||[]).find(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date),completed=rows.length,observed=completed||mark?Math.max(completed,1):0,cumulative=rows.reduce((value,row)=>value*(1+Number(row.gross_return)),1)-1,spyCumulative=rows.reduce((value,row)=>Number.isFinite(Number(row.spy_return))?value*(1+Number(row.spy_return)):value,1)-1,asOf=data.experiment.decision_date,progressLabel=completed?`${completed}/${a.horizon_sessions} complete sessions`:mark?`1/${a.horizon_sessions} observed (partial) · 0 complete`:`0/${a.horizon_sessions} observed`,status=completed?`${fmtPct(cumulative)} interim · ${fmtPct(cumulative-spyCumulative)} vs SPY`:mark?`Entry-day close mark ${fmtPct(mark.gross_mark_to_close)} · ${fmtPct(mark.active_mark_vs_spy)} vs SPY`:(asOf
${armLabel(a)}${progressLabel}
${status} · complete label ${a.expected_maturity_date}`}).join('');legend.innerHTML=groups.map(g=>``).join('');legend.querySelectorAll('[data-series]').forEach(b=>b.addEventListener('click',()=>{hiddenSeries.has(b.dataset.series)?hiddenSeries.delete(b.dataset.series):hiddenSeries.add(b.dataset.series);renderStrategy();save()}));const visible=groups.filter(g=>!hiddenSeries.has(g.id));if(!visible.length){const entries=arms.map(a=>a.expected_entry_date).filter(Boolean).sort(),marks=arms.map(a=>(data.strategy_interim_marks||[]).find(r=>r.family===a.family&&Number(r.horizon_sessions)===a.horizon_sessions&&r.rebalance_decision_date===a.decision_date)).filter(Boolean),message=marks.length?'Official entry-day close marks are shown above as one observed partial day. The cumulative chart begins after the first complete open-to-open session.':`The ${decisionSelect.value} close decision first becomes invested at the ${entries[0]||'next configured'} open.`;document.getElementById('strategyChart').innerHTML=`
No complete open-to-open session yet${message}
`;return}const lines=visible.map(g=>{let cumulative=1;return{id:g.id,points:g.rows.map(r=>({date:r.return_date,value:(cumulative*=1+Number(r.gross_return))-1}))}}),stamps=[...new Set(lines.flatMap(l=>l.points.map(p=>p.date)))].sort(),vals=lines.flatMap(l=>l.points.map(p=>p.value)),min=Math.min(0,...vals),max=Math.max(0,...vals),pad=Math.max((max-min)*.14,.001),w=1180,h=360,left=66,right=22,top=18,bottom=48,pw=w-left-right,ph=h-top-bottom,x=s=>left+stamps.indexOf(s)/Math.max(1,stamps.length-1)*pw,y=v=>top+(1-(v-(min-pad))/((max+pad)-(min-pad)||1))*ph,ticks=[0,.25,.5,.75,1].map(f=>max+pad-f*((max+pad)-(min-pad))),grid=ticks.map(v=>`${fmtPct(v)}`).join(''),paths=lines.map(line=>``).join('');document.getElementById('strategyChart').innerHTML=``}
function renderHorizons(){const all=decisionArms(),cards=[5,10,20].map(h=>{const pair=all.filter(a=>a.horizon_sessions===h).sort((a,b)=>a.family.localeCompare(b.family)),slices=pair.map(a=>`
${names[a.family]}${fmtPct(a.validation?.top40_raw_excess)}Validation top-40 excess Rank IC ${fmtNum(a.validation?.rank_ic)} · ${a.feature_count} features
`});document.getElementById('horizonGrid').innerHTML=cards.join('')}
function renderModels(){const node=document.getElementById('modelGrid');node.innerHTML=visibleArms().map(a=>{const v=a.validation||{},features=a.features.slice(0,12).map(f=>`${human(f)}`).join(''),extra=Math.max(0,a.features.length-12),shap=a.shap_summary||[],max=Math.max(...shap.map(x=>Number(x.mean_abs_shap)||0),0),chart=shap.length?`
${human(shap[0].method)} · ${shap[0].explanation_rows} current-cohort rows · mean absolute SHAP
${shap.slice(0,12).map(x=>`
${human(x.feature)}
${fmtNum(x.mean_abs_shap,4)}
`).join('')}`:`
SHAP was not captured for this frozen vintage. The July 29 artifacts remain immutable. Genuine ${a.family==='ridge'?'linear SHAP':'TreeSHAP'} summaries will be saved with each new forecast; coefficients or XGBoost gain are not substituted or mislabeled as SHAP.
`;return`
${armLabel(a)}
${a.refitted_today?'Refit today':'Frozen model'}
Training ${a.training_start||'n/a'} to ${a.training_end||'n/a'} · ${Number(a.training_rows||0).toLocaleString()} rows · ${v.completed_trials||'n/a'} eligible trials
Top-40 excess${fmtPct(v.top40_raw_excess)}
Rank IC${fmtNum(v.rank_ic)}
Positive IC${fmtPct(v.positive_ic_rate)}
Selected ${a.feature_count} features · ${human(v.selection_metric||'n/a')}
${features}${extra?`+${extra} more`:''}
${chart}
`}).join('')||'
No visible model specificationsChange the family or horizon filter.
${value}${detail}`).join('');const warnings=[];if(!survival.activated)warnings.push('Survival ML remains shadow-only; no stock is excluded by an unvalidated threshold.');if(data.rebalance_policy?.status==='under_audit')warnings.push('Full top-40 refresh remains under audit; the rank/score buffer has not been activated.');if(!data.matured.length)warnings.push('No v2 forecast has matured. Today’s successful run is operational evidence, not realized alpha.');document.getElementById('warnings').innerHTML=warnings.map(x=>`
${x}
`).join('')}
function renderMatured(){const rows=data.matured.filter(x=>(familyFilter.value==='all'||x.family===familyFilter.value)&&(horizonFilter.value==='all'||String(x.horizon_sessions)===horizonFilter.value));document.getElementById('maturedTable').innerHTML=table(rows,[['decision_date','Decision'],['family','Model'],['horizon_sessions','Horizon'],['selected_return','Selected return'],['active_return_vs_universe','Excess vs universe'],['active_return_vs_spy','Excess vs SPY']],null)}
function renderAll(){renderNotice();renderKpis();renderStrategy();renderHorizons();renderModels();renderHealth();renderMatured();save()}
[decisionSelect,familyFilter,horizonFilter].forEach(node=>node.addEventListener('change',renderAll));document.getElementById('refreshData').addEventListener('click',()=>{save();location.reload()});renderAll();
-