+ {/* Header / Engine Status */}
+
+
+
+
+ LEAN Engine
+
+
+
+
+
+
+
+
+ {status?.dockerAvailable ? 'Docker Ready' : 'Docker Offline'}
+
+ |
+ {status?.enabled ? 'Active' : 'Disabled'}
+
+
+
+ {status?.leanCliAvailable ? 'Native LEAN CLI Ready' : 'Native LEAN CLI Missing'}
+
+
+
+ {/* Main Navigation */}
+
+ Quant Research
+ }
+ active={isMainActive}
+ onClick={() => navTo({ kind: 'quant-lab', params: {} })}
+ trail={
+
+ {strategies.length}
+
+ }
+ />
+ }
+ active={isIntegrityActive}
+ onClick={() =>
+ navTo({
+ kind: 'quant-lab-integrity',
+ params: { experimentId: 'latest' }
+ })
+ }
+ trail={
+
+ Evidence
+
+ }
+ />
+ }
+ active={isJournalActive}
+ onClick={() => navTo({ kind: 'quant-lab-journal', params: {} })}
+ trail={
+
+ {status?.journalCount ?? 0}
+
+ }
+ />
+
+
+ {/* Strategies List */}
+
+
+
+ Algorithms
+
+
navTo({ kind: 'quant-lab', params: {} })}
+ title="Create new strategy"
+ className="text-muted-foreground hover:text-foreground transition-colors"
+ >
+
+
+
+ {strategies.length === 0 ? (
+
+ No strategies created yet
+
+ ) : (
+ strategies.map((strat) => (
+
}
+ active={activeStrategyId === strat.id}
+ onClick={() =>
+ navTo({
+ kind: 'quant-lab-strategy',
+ params: { id: strat.id }
+ })
+ }
+ trail={
+ strat.templateId ? (
+
+ {strat.templateId}
+
+ ) : undefined
+ }
+ />
+ ))
+ )}
+
+
+ {/* Recent Backtests */}
+
+
Recent Runs
+ {backtests.length === 0 ? (
+
+ No backtest runs found
+
+ ) : (
+ backtests.map((bt) => (
+
+ {bt.strategyName}
+
+ {bt.symbol} ยท {bt.startDate.slice(2)} to {bt.endDate.slice(2)}
+
+
+ }
+ icon={
}
+ active={activeBacktestId === bt.id}
+ onClick={() =>
+ navTo({
+ kind: 'quant-lab-results',
+ params: { id: bt.id }
+ })
+ }
+ trail={
+ bt.sharpeRatio != null ? (
+
= 1.5
+ ? 'text-success'
+ : bt.sharpeRatio > 0
+ ? 'text-primary'
+ : 'text-destructive'
+ }`}
+ >
+ SR {bt.sharpeRatio.toFixed(1)}
+
+ ) : (
+
+ {bt.status}
+
+ )
+ }
+ />
+ ))
+ )}
+
+
+ )
+}
diff --git a/ui/src/components/lean/ResearchIntegrityCards.tsx b/ui/src/components/lean/ResearchIntegrityCards.tsx
new file mode 100644
index 000000000..5cf9f2a75
--- /dev/null
+++ b/ui/src/components/lean/ResearchIntegrityCards.tsx
@@ -0,0 +1,346 @@
+import type { ResearchIntegrityReport } from '../../api/lean'
+import {
+ ShieldAlert,
+ ShieldCheck,
+ Binary,
+ Layers,
+ Repeat,
+ AlertOctagon,
+ BookOpen,
+ Info,
+ Sliders,
+ TrendingDown
+} from 'lucide-react'
+
+interface ResearchIntegrityCardsProps {
+ report?: ResearchIntegrityReport
+}
+
+export function ResearchIntegrityCards({ report }: ResearchIntegrityCardsProps) {
+ if (!report) {
+ return (
+
+ {/* Evidence-First Methodology Notice */}
+
+
+
+ Evidence-First Integrity Guarantee: {' '}
+ {report.methodologyNotice}
+
+
+
+ {/* Summary Findings */}
+ {report.summaryFindings && report.summaryFindings.length > 0 && (
+
+
+ Core Empirical Findings
+
+
+ {report.summaryFindings.map((finding, idx) => (
+
+ โข
+ {finding}
+
+ ))}
+
+
+ )}
+
+ {/* 2-Column Grid for Specific Integrity Checks */}
+
+ {/* 1. Out-of-Sample (OOS) Validation */}
+ {outOfSample && (
+
+
+
+
+
+
+
+
Out-of-Sample (OOS) Split
+
+
+ {outOfSample.sharpeDegradationPct.toFixed(1)}% Degradation
+
+
+
+
+
+
In-Sample (IS)
+
+ SR {outOfSample.isSharpe.toFixed(2)}
+
+
+ Profit: ${(outOfSample.isNetProfit || 0).toLocaleString()}
+
+
+
+
+
Out-of-Sample (OOS)
+
+ SR {outOfSample.oosSharpe.toFixed(2)}
+
+
+ Profit: ${(outOfSample.oosNetProfit || 0).toLocaleString()}
+
+
+
+
+ {outOfSample.deflatedSharpeRatio && (
+
+
+ Deflated Sharpe Ratio (DSR):
+
+ {(outOfSample.deflatedSharpeRatio.dsr * 100).toFixed(1)}%
+
+
+
+ Accounts for skewness ({outOfSample.deflatedSharpeRatio.skewness}), kurtosis (
+ {outOfSample.deflatedSharpeRatio.kurtosis}), and trials (
+ {outOfSample.deflatedSharpeRatio.trialsTested}).
+
+
+ )}
+
+
{outOfSample.interpretation}
+
+
+ {outOfSample.academicReferences && (
+
+ Ref: {outOfSample.academicReferences[0]}
+
+ )}
+
+ )}
+
+ {/* 2. Walk-Forward Analysis */}
+ {walkForward && (
+
+
+
+
+
+
+
+
Walk-Forward Efficiency
+
+
= 50
+ ? 'bg-success/15 text-success'
+ : 'bg-warning/15 text-warning'
+ }`}
+ >
+ WFE {walkForward.walkForwardEfficiency.toFixed(1)}%
+
+
+
+
+
+
Windows
+
{walkForward.windowCount}
+
+
+
Positive OOS
+
+ {(walkForward.positiveOosWindowRatio * 100).toFixed(0)}%
+
+
+
+
Max OOS DD
+
+ {(walkForward.maxOosDrawdown * 100).toFixed(1)}%
+
+
+
+
+
{walkForward.interpretation}
+
+
+ {walkForward.academicReferences && (
+
+ Ref: {walkForward.academicReferences[0]}
+
+ )}
+
+ )}
+
+ {/* 3. Monte Carlo Simulation */}
+ {monteCarlo && (
+
+
+
+
+
+
+
+
+ Monte Carlo ({monteCarlo.iterations} Paths)
+
+
+
+ Ruin Prob: {(monteCarlo.ruinProbability * 100).toFixed(1)}%
+
+
+
+
+
+ Max Drawdown Percentiles:
+
+ p50: {(monteCarlo.maxDrawdownDistribution.p50 * 100).toFixed(1)}% | p95:{' '}
+
+ {(monteCarlo.maxDrawdownDistribution.p95 * 100).toFixed(1)}%
+
+
+
+
+
+ Final Return Percentiles:
+
+ p50: {(monteCarlo.finalReturnDistribution.p50 * 100).toFixed(1)}% | p05:{' '}
+ {(monteCarlo.finalReturnDistribution.p05 * 100).toFixed(1)}%
+
+
+
+
+ Longest Losing Streak:
+
+ Median: {monteCarlo.longestLosingStreakDistribution.median} trades (95th: {monteCarlo.longestLosingStreakDistribution.p95})
+
+
+
+
+
+ {monteCarlo.academicReferences && (
+
+ Ref: {monteCarlo.academicReferences[0]}
+
+ )}
+
+ )}
+
+ {/* 4. Data Snooping & Multiple Testing */}
+ {dataSnooping && (
+
+
+
+
+
+
Data Snooping Penalty
+
+
+ {dataSnooping.isSignificantAfterCorrection ? 'Significant' : 'Spurious Risk'}
+
+
+
+
+
+
Historical Trials
+
{dataSnooping.totalHistoricalTrials}
+
+ Exp. False: {dataSnooping.expectedFalseDiscoveries}
+
+
+
+
+
Haircut Sharpe
+
+ SR {dataSnooping.haircutSharpeRatio.toFixed(2)}
+
+
+ p_adj: {dataSnooping.holmAdjustedPValue}
+
+
+
+
+
{dataSnooping.interpretation}
+
+
+ {dataSnooping.academicReferences && (
+
+ Ref: {dataSnooping.academicReferences[0]}
+
+ )}
+
+ )}
+
+ {/* 5. Parameter Sensitivity */}
+ {sensitivity && (
+
+
+
+
+
+
Parameter Fragility & Elasticity
+
+
+
+ {Object.entries(sensitivity.parameterFragility).map(([paramName, frag]) => (
+
+
+ {paramName}
+
+ {frag.isUnstable ? 'Unstable Cliff' : 'Robust'}
+
+
+
+ Max Drop: {frag.maxSharpeDropPct}%
+
+
+ Elasticity: {frag.averageElasticity}
+
+
+ ))}
+
+
+
{sensitivity.interpretation}
+
+ )}
+
+
+ )
+}
diff --git a/ui/src/components/lean/TradeLogTable.tsx b/ui/src/components/lean/TradeLogTable.tsx
new file mode 100644
index 000000000..81257abb3
--- /dev/null
+++ b/ui/src/components/lean/TradeLogTable.tsx
@@ -0,0 +1,242 @@
+import { useState, useMemo } from 'react'
+import type { ClosedTrade, LeanOrder } from '../../api/lean'
+import { SegmentedControl } from '../SegmentedControl'
+import { Search, ArrowDownRight, ArrowUpRight, ArrowUpDown } from 'lucide-react'
+
+interface TradeLogTableProps {
+ closedTrades?: ClosedTrade[]
+ orders?: LeanOrder[]
+}
+
+export function TradeLogTable({ closedTrades = [], orders = [] }: TradeLogTableProps) {
+ const [tab, setTab] = useState<'trades' | 'orders'>('trades')
+ const [search, setSearch] = useState('')
+ const [sortField, setSortField] = useState