chore: remove dead frontend code and one unused service helper
Scan of every module and exported symbol, with each candidate verified by hand
rather than trusted from the scan.
Deleted outright:
frontend/src/lib/fundamentals.ts (112 lines, 12 exports) — imported by
nothing, including FundamentalsPanel, which reads backend values. It mirrors
scoring_service._compute_fundamental_score, so it is the same *kind* of
thing as lib/qualification.ts — but nothing consumes it, so it mirrored
nothing and could drift out of sync unnoticed.
Skeleton.SkeletonLine, paperTrades.getEquityCurve, regime.regimeColor
breadth_service.compute_breadth_today — self-described "thin wrapper, for
future live use"; that future did not arrive.
Kept, but unexported — used inside their own module, so the dead part was the
public surface, not the code: Button.Spinner, exitPlan.SETUP_STOP_ATR_MULTIPLIER,
client.ApiError.
Three things the scan flagged that are NOT dead, recorded so the next sweep does
not re-raise them:
RegimeChart.tsx — lazy(() => import(...)) in RegimePage, so it looks orphaned
to any importer-graph scan. Deleting it would break the risk page.
qualification.ts MIN_TARGET_PROBABILITY / liveRiskReward — that file is a live
mirror of app/services/qualification.py used in five places, and the
constant is exported to document the backend value it tracks.
ssl_bootstrap.ssl_status — called from an inline python snippet inside
scripts/run_tier1_macbook.sh, invisible to a .py-only search.
No orphaned backend modules across app/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -148,11 +148,3 @@ async def compute_breadth_details(
|
|||||||
"""Breadth values plus the qualifying-member count for snapshot metadata."""
|
"""Breadth values plus the qualifying-member count for snapshot metadata."""
|
||||||
closes_by_symbol = await _load_universe_closes(db, symbols)
|
closes_by_symbol = await _load_universe_closes(db, symbols)
|
||||||
return _breadth_with_counts(closes_by_symbol, window, min_tickers)
|
return _breadth_with_counts(closes_by_symbol, window, min_tickers)
|
||||||
|
|
||||||
|
|
||||||
async def compute_breadth_today(db: AsyncSession) -> float | None:
|
|
||||||
"""Latest breadth reading (thin wrapper, for future live use)."""
|
|
||||||
series = await compute_breadth_series(db)
|
|
||||||
if not series:
|
|
||||||
return None
|
|
||||||
return series[max(series)]
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useAuthStore } from '../stores/authStore';
|
|||||||
* Typed error class for API errors, providing structured error handling
|
* Typed error class for API errors, providing structured error handling
|
||||||
* across the application.
|
* across the application.
|
||||||
*/
|
*/
|
||||||
export class ApiError extends Error {
|
class ApiError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ApiError';
|
this.name = 'ApiError';
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ export interface EquityPoint {
|
|||||||
benchmark_pnl: number;
|
benchmark_pnl: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEquityCurve() {
|
|
||||||
return apiClient.get<EquityPoint[]>('paper-trades/equity-curve').then((r) => r.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerfPoint {
|
export interface PerfPoint {
|
||||||
date: string;
|
date: string;
|
||||||
manual_pnl: number;
|
manual_pnl: number;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const sizeClasses: Record<Size, string> = {
|
|||||||
md: 'px-4 py-2 text-sm',
|
md: 'px-4 py-2 text-sm',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Spinner({ className = 'h-4 w-4' }: { className?: string }) {
|
function Spinner({ className = 'h-4 w-4' }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<svg className={`animate-spin ${className}`} viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
<svg className={`animate-spin ${className}`} viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
const pulse = 'animate-pulse rounded-lg bg-white/[0.05]';
|
const pulse = 'animate-pulse rounded-lg bg-white/[0.05]';
|
||||||
|
|
||||||
export function SkeletonLine({ className = '' }: { className?: string }) {
|
|
||||||
return <div className={`${pulse} h-4 w-full ${className}`} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SkeletonCard({ className = '' }: { className?: string }) {
|
export function SkeletonCard({ className = '' }: { className?: string }) {
|
||||||
return <div className={`${pulse} h-32 w-full ${className}`} />;
|
return <div className={`${pulse} h-32 w-full ${className}`} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import type { ExitPolicy, TradeSetup } from './types';
|
|||||||
* Guarded by test_prod_strategy_parity.py so a backend change can't silently
|
* Guarded by test_prod_strategy_parity.py so a backend change can't silently
|
||||||
* desync this.
|
* desync this.
|
||||||
*/
|
*/
|
||||||
export const SETUP_STOP_ATR_MULTIPLIER = 1.5;
|
const SETUP_STOP_ATR_MULTIPLIER = 1.5;
|
||||||
|
|
||||||
export interface ExitPlan {
|
export interface ExitPlan {
|
||||||
mode: ExitPolicy['mode'];
|
mode: ExitPolicy['mode'];
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fundamental dimension readouts for the Fundamentals tab.
|
|
||||||
*
|
|
||||||
* Scoring mirrors app/services/scoring_service.py _compute_fundamental_score:
|
|
||||||
* equal-weighted average of available P/E, revenue growth, and earnings
|
|
||||||
* surprise (need ≥2 metrics). Market cap is display-only, not scored.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface FundamentalMetrics {
|
|
||||||
pe_ratio: number | null;
|
|
||||||
revenue_growth: number | null;
|
|
||||||
earnings_surprise: number | null;
|
|
||||||
market_cap?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StatusRead {
|
|
||||||
text: string;
|
|
||||||
tone: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clamp = (v: number, lo = 0, hi = 100) => Math.max(lo, Math.min(hi, v));
|
|
||||||
|
|
||||||
/** P/E sub-score: lower is better. PE 15 → 100, 30 → 50, 45 → 0. */
|
|
||||||
export function peSubScore(pe: number): number {
|
|
||||||
return clamp(100 - (pe - 15) * (100 / 30));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Revenue growth sub-score: 0% → 50, +20% → 100, −20% → 0. */
|
|
||||||
export function revenueGrowthSubScore(growthPct: number): number {
|
|
||||||
return clamp(50 + growthPct * 2.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Earnings surprise sub-score: 0% → 50, +10% → 100, −10% → 0. */
|
|
||||||
export function earningsSurpriseSubScore(surprisePct: number): number {
|
|
||||||
return clamp(50 + surprisePct * 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overall fundamental score, or null when fewer than 2 scored metrics.
|
|
||||||
* Matches backend MIN_METRICS = 2.
|
|
||||||
*/
|
|
||||||
export function fundamentalScore(m: FundamentalMetrics): number | null {
|
|
||||||
const parts: number[] = [];
|
|
||||||
if (m.pe_ratio != null && m.pe_ratio > 0) parts.push(peSubScore(m.pe_ratio));
|
|
||||||
if (m.revenue_growth != null) parts.push(revenueGrowthSubScore(m.revenue_growth));
|
|
||||||
if (m.earnings_surprise != null) parts.push(earningsSurpriseSubScore(m.earnings_surprise));
|
|
||||||
if (parts.length < 2) return null;
|
|
||||||
return parts.reduce((a, b) => a + b, 0) / parts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function overallFundamentalStatus(score: number | null): StatusRead {
|
|
||||||
if (score == null) {
|
|
||||||
return { text: 'incomplete data', tone: 'text-amber-300' };
|
|
||||||
}
|
|
||||||
if (score >= 70) return { text: 'strong fundamentals', tone: 'text-emerald-300' };
|
|
||||||
if (score >= 55) return { text: 'healthy', tone: 'text-emerald-300' };
|
|
||||||
if (score >= 45) return { text: 'mixed / average', tone: 'text-gray-400' };
|
|
||||||
if (score >= 30) return { text: 'soft', tone: 'text-amber-300' };
|
|
||||||
return { text: 'weak fundamentals', tone: 'text-red-300' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function peStatus(pe: number | null): StatusRead | null {
|
|
||||||
if (pe == null || !(pe > 0)) return null;
|
|
||||||
if (pe <= 15) return { text: 'cheap / attractive', tone: 'text-emerald-300' };
|
|
||||||
if (pe <= 25) return { text: 'fair', tone: 'text-gray-400' };
|
|
||||||
if (pe <= 35) return { text: 'expensive', tone: 'text-amber-300' };
|
|
||||||
return { text: 'rich', tone: 'text-red-300' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function revenueGrowthStatus(growthPct: number | null): StatusRead | null {
|
|
||||||
if (growthPct == null) return null;
|
|
||||||
if (growthPct >= 20) return { text: 'strong growth', tone: 'text-emerald-300' };
|
|
||||||
if (growthPct >= 5) return { text: 'solid growth', tone: 'text-emerald-300' };
|
|
||||||
if (growthPct >= -5) return { text: 'flat', tone: 'text-gray-400' };
|
|
||||||
if (growthPct >= -20) return { text: 'contracting', tone: 'text-amber-300' };
|
|
||||||
return { text: 'deep contraction', tone: 'text-red-300' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function earningsSurpriseStatus(surprisePct: number | null): StatusRead | null {
|
|
||||||
if (surprisePct == null) return null;
|
|
||||||
if (surprisePct >= 10) return { text: 'beat (large)', tone: 'text-emerald-300' };
|
|
||||||
if (surprisePct >= 2) return { text: 'beat', tone: 'text-emerald-300' };
|
|
||||||
if (surprisePct >= -2) return { text: 'in line', tone: 'text-gray-400' };
|
|
||||||
if (surprisePct >= -10) return { text: 'miss', tone: 'text-amber-300' };
|
|
||||||
return { text: 'miss (large)', tone: 'text-red-300' };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Size band only — not good/bad, not part of the score. */
|
|
||||||
export function marketCapStatus(marketCap: number | null): StatusRead | null {
|
|
||||||
if (marketCap == null || !(marketCap > 0)) return null;
|
|
||||||
if (marketCap >= 200e9) return { text: 'mega cap', tone: 'text-gray-400' };
|
|
||||||
if (marketCap >= 10e9) return { text: 'large cap', tone: 'text-gray-400' };
|
|
||||||
if (marketCap >= 2e9) return { text: 'mid cap', tone: 'text-gray-400' };
|
|
||||||
if (marketCap >= 300e6) return { text: 'small cap', tone: 'text-gray-400' };
|
|
||||||
return { text: 'micro cap', tone: 'text-gray-400' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function metricStatus(
|
|
||||||
key: 'pe_ratio' | 'revenue_growth' | 'earnings_surprise' | 'market_cap',
|
|
||||||
value: number | null,
|
|
||||||
): StatusRead | null {
|
|
||||||
switch (key) {
|
|
||||||
case 'pe_ratio':
|
|
||||||
return peStatus(value);
|
|
||||||
case 'revenue_growth':
|
|
||||||
return revenueGrowthStatus(value);
|
|
||||||
case 'earnings_surprise':
|
|
||||||
return earningsSurpriseStatus(value);
|
|
||||||
case 'market_cap':
|
|
||||||
return marketCapStatus(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,5 @@
|
|||||||
import type { MarketRegime } from './types';
|
import type { MarketRegime } from './types';
|
||||||
|
|
||||||
export function regimeColor(label: MarketRegime['label']): string {
|
|
||||||
switch (label) {
|
|
||||||
case 'bullish':
|
|
||||||
return 'text-emerald-400';
|
|
||||||
case 'bearish':
|
|
||||||
return 'text-red-400';
|
|
||||||
case 'neutral':
|
|
||||||
return 'text-amber-400';
|
|
||||||
default:
|
|
||||||
return 'text-gray-400';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function regimeDot(label: MarketRegime['label']): string {
|
export function regimeDot(label: MarketRegime['label']): string {
|
||||||
switch (label) {
|
switch (label) {
|
||||||
case 'bullish':
|
case 'bullish':
|
||||||
|
|||||||
Reference in New Issue
Block a user