Runn the MVP of the app
This commit is contained in:
parent
09abe2da38
commit
60c0970c2e
|
|
@ -3,7 +3,7 @@ __pycache__/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.csv
|
*.csv
|
||||||
# src/
|
# src/
|
||||||
# eval_results/
|
eval_results/
|
||||||
eval_data/
|
eval_data/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, Optional
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import asyncio
|
from datetime import datetime
|
||||||
from datetime import datetime, date
|
|
||||||
import glob
|
import glob
|
||||||
from pathlib import Path
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
# Import your TradingAgents components
|
# Import your TradingAgents components
|
||||||
|
|
@ -18,6 +16,9 @@ from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
|
||||||
app = FastAPI(title="TradingAgents API", version="1.0.0")
|
app = FastAPI(title="TradingAgents API", version="1.0.0")
|
||||||
|
|
||||||
|
# Centralized results directory to avoid repetition
|
||||||
|
RESULTS_BASE = "/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results"
|
||||||
|
|
||||||
# Configure CORS
|
# Configure CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|
@ -69,6 +70,8 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
|
||||||
|
|
||||||
# Initialize TradingAgents
|
# Initialize TradingAgents
|
||||||
jobs[job_id].progress = "Setting up trading graph..."
|
jobs[job_id].progress = "Setting up trading graph..."
|
||||||
|
|
||||||
|
# Do not set API keys in code. Use environment variables or a secure secret manager.
|
||||||
ta = TradingAgentsGraph(debug=True, config=config)
|
ta = TradingAgentsGraph(debug=True, config=config)
|
||||||
|
|
||||||
# Run the analysis
|
# Run the analysis
|
||||||
|
|
@ -93,33 +96,37 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
|
||||||
async def start_analysis(request: AnalysisRequest, background_tasks: BackgroundTasks):
|
async def start_analysis(request: AnalysisRequest, background_tasks: BackgroundTasks):
|
||||||
"""Start a new trading analysis"""
|
"""Start a new trading analysis"""
|
||||||
job_id = str(uuid.uuid4())
|
job_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Normalize inputs
|
||||||
|
symbol = request.symbol.upper().strip()
|
||||||
|
date = request.date.strip()
|
||||||
|
|
||||||
# Validate date format
|
# Validate date format
|
||||||
try:
|
try:
|
||||||
datetime.strptime(request.date, "%Y-%m-%d")
|
datetime.strptime(date, "%Y-%m-%d")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
|
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
|
||||||
|
|
||||||
# Initialize job
|
# Initialize job
|
||||||
jobs[job_id] = JobStatus(
|
jobs[job_id] = JobStatus(
|
||||||
job_id=job_id,
|
job_id=job_id,
|
||||||
status="queued",
|
status="queued",
|
||||||
progress="Analysis queued"
|
progress="Analysis queued"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Start background task
|
# Start background task
|
||||||
background_tasks.add_task(
|
background_tasks.add_task(
|
||||||
run_analysis_task,
|
run_analysis_task,
|
||||||
job_id,
|
job_id,
|
||||||
request.symbol.upper(),
|
symbol,
|
||||||
request.date,
|
date,
|
||||||
request.config_overrides or {}
|
request.config_overrides or {}
|
||||||
)
|
)
|
||||||
|
|
||||||
return AnalysisResponse(
|
return AnalysisResponse(
|
||||||
job_id=job_id,
|
job_id=job_id,
|
||||||
status="queued",
|
status="queued",
|
||||||
message=f"Analysis started for {request.symbol} on {request.date}"
|
message=f"Analysis started for {symbol} on {date}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/analysis/status/{job_id}", response_model=JobStatus)
|
@app.get("/analysis/status/{job_id}", response_model=JobStatus)
|
||||||
|
|
@ -133,7 +140,7 @@ async def get_analysis_status(job_id: str):
|
||||||
@app.get("/results/companies")
|
@app.get("/results/companies")
|
||||||
async def get_companies():
|
async def get_companies():
|
||||||
"""Get list of companies with analysis results"""
|
"""Get list of companies with analysis results"""
|
||||||
results_dir = "/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results"
|
results_dir = RESULTS_BASE
|
||||||
|
|
||||||
if not os.path.exists(results_dir):
|
if not os.path.exists(results_dir):
|
||||||
return {"companies": []}
|
return {"companies": []}
|
||||||
|
|
@ -176,7 +183,7 @@ async def get_companies():
|
||||||
@app.get("/results/{symbol}")
|
@app.get("/results/{symbol}")
|
||||||
async def get_company_results(symbol: str):
|
async def get_company_results(symbol: str):
|
||||||
"""Get all analysis results for a specific company"""
|
"""Get all analysis results for a specific company"""
|
||||||
results_dir = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_logs"
|
results_dir = os.path.join(RESULTS_BASE, symbol.upper(), "TradingAgentsStrategy_logs")
|
||||||
|
|
||||||
if not os.path.exists(results_dir):
|
if not os.path.exists(results_dir):
|
||||||
raise HTTPException(status_code=404, detail=f"No results found for {symbol}")
|
raise HTTPException(status_code=404, detail=f"No results found for {symbol}")
|
||||||
|
|
@ -214,7 +221,7 @@ async def get_company_results(symbol: str):
|
||||||
@app.get("/transformed-results/{symbol}")
|
@app.get("/transformed-results/{symbol}")
|
||||||
async def get_transformed_company_results(symbol: str):
|
async def get_transformed_company_results(symbol: str):
|
||||||
"""Get all transformed analysis results for a specific company"""
|
"""Get all transformed analysis results for a specific company"""
|
||||||
results_dir = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_transformed_logs"
|
results_dir = os.path.join(RESULTS_BASE, symbol.upper(), "TradingAgentsStrategy_transformed_logs")
|
||||||
|
|
||||||
if not os.path.exists(results_dir):
|
if not os.path.exists(results_dir):
|
||||||
raise HTTPException(status_code=404, detail=f"No transformed results found for {symbol}")
|
raise HTTPException(status_code=404, detail=f"No transformed results found for {symbol}")
|
||||||
|
|
@ -255,7 +262,12 @@ async def get_transformed_company_results(symbol: str):
|
||||||
@app.get("/results/{symbol}/{date}")
|
@app.get("/results/{symbol}/{date}")
|
||||||
async def get_specific_result(symbol: str, date: str):
|
async def get_specific_result(symbol: str, date: str):
|
||||||
"""Get specific analysis result"""
|
"""Get specific analysis result"""
|
||||||
file_path = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_logs/full_states_log_{date}.json"
|
file_path = os.path.join(
|
||||||
|
RESULTS_BASE,
|
||||||
|
symbol.upper(),
|
||||||
|
"TradingAgentsStrategy_logs",
|
||||||
|
f"full_states_log_{date}.json",
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
raise HTTPException(status_code=404, detail=f"No result found for {symbol} on {date}")
|
raise HTTPException(status_code=404, detail=f"No result found for {symbol} on {date}")
|
||||||
|
|
@ -279,7 +291,12 @@ async def get_specific_result(symbol: str, date: str):
|
||||||
@app.get("/transformed-results/{symbol}/{date}")
|
@app.get("/transformed-results/{symbol}/{date}")
|
||||||
async def get_specific_transformed_result(symbol: str, date: str):
|
async def get_specific_transformed_result(symbol: str, date: str):
|
||||||
"""Get specific transformed analysis result"""
|
"""Get specific transformed analysis result"""
|
||||||
file_path = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_transformed_logs/full_states_log_{date}_transformed.json"
|
file_path = os.path.join(
|
||||||
|
RESULTS_BASE,
|
||||||
|
symbol.upper(),
|
||||||
|
"TradingAgentsStrategy_transformed_logs",
|
||||||
|
f"full_states_log_{date}_transformed.json",
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}")
|
raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}")
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@ function App() {
|
||||||
const runAnalysis = async () => {
|
const runAnalysis = async () => {
|
||||||
setIsRunningAnalysis(true);
|
setIsRunningAnalysis(true);
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/run-analysis', analysisForm);
|
const response = await axios.post('/analysis/start', analysisForm);
|
||||||
alert('Analysis completed successfully!');
|
alert('Analysis completed successfully!');
|
||||||
setShowAnalysisModal(false);
|
setShowAnalysisModal(false);
|
||||||
setAnalysisForm({ symbol: '', date: '' });
|
setAnalysisForm({ symbol: '', date: '' });
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
|
|
||||||
|
|
||||||
// New interface for the transformed JSON structure
|
// New interface for the transformed JSON structure
|
||||||
interface TransformedAnalysisData {
|
interface TransformedAnalysisData {
|
||||||
|
|
@ -379,58 +378,226 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
||||||
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
|
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Minimalist Technical Indicators widget (single-card dashboard)
|
// Minimalist dashboard that shows ALL main sections of transformed JSON
|
||||||
const TechnicalIndicatorsOnly: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
|
const MinimalTransformedDashboard: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
|
||||||
const ti = data?.technical_indicators || {} as any;
|
const md = data?.metadata as any || {};
|
||||||
|
const fd = data?.financial_data as any || {};
|
||||||
|
const ti = data?.technical_indicators as any || {};
|
||||||
|
const istrat = data?.investment_strategy as any || {};
|
||||||
|
const ds = data?.debate_summary as any || {};
|
||||||
|
const txt = data?.text_content as any || {};
|
||||||
|
|
||||||
const trends = ti?.trend_directions || {};
|
const trends = ti?.trend_directions || {};
|
||||||
const fmt = (v: any, d = 0) => {
|
const fmt = (v: any, d = 0) => {
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
return Number.isFinite(n) ? n.toFixed(d) : '—';
|
if (v === null || v === undefined || Number.isNaN(n)) return '-';
|
||||||
|
return Number.isFinite(n) ? n.toFixed(d) : String(v);
|
||||||
};
|
};
|
||||||
const trendChip = (label: string, dir?: string) => {
|
const fmtNumber = (v: any, d = 0) => {
|
||||||
const map: Record<string, string> = { BULLISH: 'bg-green-100 text-green-800', BEARISH: 'bg-red-100 text-red-800', NEUTRAL: 'bg-gray-100 text-gray-700' };
|
const n = Number(v);
|
||||||
const cls = map[String(dir || 'NEUTRAL').toUpperCase()] || map.NEUTRAL;
|
if (!Number.isFinite(n)) return '-';
|
||||||
return <span className={`px-2 py-1 rounded text-xs font-medium ${cls}`}>{label}: {dir || 'NEUTRAL'}</span>;
|
return n.toLocaleString(undefined, { maximumFractionDigits: d, minimumFractionDigits: d });
|
||||||
};
|
};
|
||||||
|
const fmtCurrency = (v: any, d = 2) => {
|
||||||
|
const n = Number(v);
|
||||||
|
if (!Number.isFinite(n)) return '-';
|
||||||
|
return n.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: d, minimumFractionDigits: d });
|
||||||
|
};
|
||||||
|
const fmtPercent = (v: any, d = 2) => {
|
||||||
|
const n = Number(v);
|
||||||
|
if (!Number.isFinite(n)) return '-';
|
||||||
|
return `${n.toFixed(d)}%`;
|
||||||
|
};
|
||||||
|
const toneFromNumber = (v: any): 'pos' | 'neg' | 'neutral' => {
|
||||||
|
const n = Number(v);
|
||||||
|
if (!Number.isFinite(n)) return 'neutral';
|
||||||
|
if (n > 0) return 'pos';
|
||||||
|
if (n < 0) return 'neg';
|
||||||
|
return 'neutral';
|
||||||
|
};
|
||||||
|
const Stat: React.FC<{ label: string; value: React.ReactNode; sub?: React.ReactNode; tone?: 'pos' | 'neg' | 'neutral' }>
|
||||||
|
= ({ label, value, sub, tone = 'neutral' }) => {
|
||||||
|
const toneCls = tone === 'pos' ? 'text-green-600' : tone === 'neg' ? 'text-red-600' : 'text-gray-900';
|
||||||
|
const ringCls = tone === 'pos' ? 'ring-green-200' : tone === 'neg' ? 'ring-red-200' : 'ring-gray-200';
|
||||||
|
return (
|
||||||
|
<div className={`rounded-lg border border-gray-200 ring-1 ${ringCls} p-4 bg-white shadow-sm`}>
|
||||||
|
<p className="text-xs text-gray-500">{label}</p>
|
||||||
|
<p className={`text-xl font-semibold ${toneCls}`}>{value}</p>
|
||||||
|
{sub && <p className="text-xs text-gray-500 mt-0.5">{sub}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const row = (label: string, value: any, fmtDigits?: number) => (
|
||||||
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<span className="text-sm text-gray-500">{label}</span>
|
||||||
|
<span className="text-sm font-medium">{fmtDigits === undefined ? (value ?? '-') : fmt(value, fmtDigits)}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const chip = (label: string, val?: string) => (
|
||||||
|
<span className="px-2 py-1 rounded bg-gray-100 text-gray-700 text-xs font-medium">{label}: {val ?? '-'}</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tabs state and memoized content for text-heavy sections
|
||||||
|
const debateTabs = React.useMemo(() => ([
|
||||||
|
{ key: 'bull', label: 'Bull', content: Array.isArray(ds?.bull_key_points) ? ds.bull_key_points : [] as any[] },
|
||||||
|
{ key: 'bear', label: 'Bear', content: Array.isArray(ds?.bear_key_points) ? ds.bear_key_points : [] as any[] },
|
||||||
|
{ key: 'neutral', label: 'Neutral', content: ds?.neutral_perspective ? [ds.neutral_perspective] : [] as any[] },
|
||||||
|
{ key: 'final', label: 'Final', content: ds?.final_decision_rationale ? [ds.final_decision_rationale] : [] as any[] },
|
||||||
|
]), [ds]);
|
||||||
|
const firstDebateWithContent = React.useMemo(
|
||||||
|
() => debateTabs.find(t => (t.content?.length ?? 0) > 0)?.key || 'bull',
|
||||||
|
[debateTabs]
|
||||||
|
);
|
||||||
|
const [activeDebateTab, setActiveDebateTab] = React.useState<string>(firstDebateWithContent);
|
||||||
|
React.useEffect(() => { setActiveDebateTab(firstDebateWithContent); }, [firstDebateWithContent]);
|
||||||
|
|
||||||
|
const reportTabsAll = React.useMemo(() => ([
|
||||||
|
{ key: 'market', title: txt?.market_report?.title || 'Market Report', bullets: txt?.market_report?.key_takeaways, content: txt?.market_report?.content },
|
||||||
|
{ key: 'sentiment', title: txt?.sentiment_report?.title || 'Sentiment Report', bullets: txt?.sentiment_report?.recent_developments, content: txt?.sentiment_report?.content },
|
||||||
|
{ key: 'fundamentals', title: txt?.fundamentals_report?.title || 'Fundamentals Report', bullets: txt?.fundamentals_report?.financial_highlights, content: txt?.fundamentals_report?.content },
|
||||||
|
]), [txt]);
|
||||||
|
const availableReports = React.useMemo(
|
||||||
|
() => reportTabsAll.filter(t => t.title || (Array.isArray(t.bullets) && t.bullets.length) || t.content),
|
||||||
|
[reportTabsAll]
|
||||||
|
);
|
||||||
|
const [activeReportTab, setActiveReportTab] = React.useState<string>(availableReports[0]?.key || 'market');
|
||||||
|
React.useEffect(() => { setActiveReportTab(availableReports[0]?.key || 'market'); }, [availableReports]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[200px] p-6">
|
<div className="p-6">
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="mx-auto max-w-6xl space-y-4">
|
||||||
<div className="bg-white rounded-lg shadow-md p-6">
|
{/* Metadata - Full width */}
|
||||||
<h2 className="text-xl font-semibold mb-4">Technical Indicators</h2>
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<h2 className="text-lg font-semibold mb-3">Company Information</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<Stat label="Company" value={`${md?.company_name ?? '-'} (${md?.company_ticker ?? '-'})`} />
|
||||||
|
<Stat label="Analysis Date" value={md?.analysis_date ?? '-'} />
|
||||||
|
<Stat label="Final Recommendation" value={md?.final_recommendation ?? '-'} />
|
||||||
|
<Stat label="Confidence" value={md?.confidence_level ?? '-'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Two column layout for remaining sections */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
{/* Debate Summary with sub-tabs */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-3">Debate Summary</h2>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">RSI</p>
|
<div className="flex gap-2 border-b border-gray-200 mb-3">
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.rsi, 2)}</p>
|
{debateTabs.map(t => (
|
||||||
</div>
|
<button
|
||||||
<div>
|
key={t.key}
|
||||||
<p className="text-sm text-gray-500">MACD</p>
|
onClick={() => setActiveDebateTab(t.key)}
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.macd, 3)}</p>
|
className={`px-3 py-1.5 text-sm rounded-t ${activeDebateTab === t.key ? 'bg-gray-100 text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
<p className="text-xs text-gray-500">Signal: {fmt(ti?.macd_signal, 3)}</p>
|
>{t.label}</button>
|
||||||
</div>
|
))}
|
||||||
<div>
|
</div>
|
||||||
<p className="text-sm text-gray-500">SMA 50</p>
|
<div className="text-sm text-gray-800 space-y-2 max-h-64 overflow-auto pr-1">
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.sma_50, 2)}</p>
|
{debateTabs.find(t => t.key === activeDebateTab)?.content?.length ? (
|
||||||
</div>
|
<ul className="list-disc list-inside">
|
||||||
<div>
|
{(debateTabs.find(t => t.key === activeDebateTab)?.content as any[]).map((c, i) => <li key={i}>{c}</li>)}
|
||||||
<p className="text-sm text-gray-500">SMA 200</p>
|
</ul>
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.sma_200, 2)}</p>
|
) : (
|
||||||
</div>
|
<p className="text-gray-500">No content.</p>
|
||||||
{ti?.ema_10 !== undefined && (
|
)}
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">EMA 10</p>
|
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.ema_10, 2)}</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-gray-500">ATR</p>
|
|
||||||
<p className="text-lg font-semibold">{fmt(ti?.atr, 3)}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex flex-wrap gap-2">
|
|
||||||
{trendChip('SMA 50', trends?.sma_50)}
|
{/* Text Content with sub-tabs */}
|
||||||
{trendChip('SMA 200', trends?.sma_200)}
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
{trends?.ema_10 !== undefined && trendChip('EMA 10', trends?.ema_10)}
|
<h2 className="text-lg font-semibold mb-3">Reports</h2>
|
||||||
{trends?.price_action !== undefined && trendChip('Price', trends?.price_action)}
|
{!availableReports.length ? (
|
||||||
|
<p className="text-sm text-gray-500">No reports available.</p>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="flex gap-2 border-b border-gray-200 mb-3">
|
||||||
|
{availableReports.map(t => (
|
||||||
|
<button
|
||||||
|
key={t.key}
|
||||||
|
onClick={() => setActiveReportTab(t.key)}
|
||||||
|
className={`px-3 py-1.5 text-sm rounded-t ${activeReportTab === t.key ? 'bg-gray-100 text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
|
||||||
|
>{t.title}</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{(() => {
|
||||||
|
const activeTab = availableReports.find(t => t.key === activeReportTab);
|
||||||
|
if (!activeTab) return null;
|
||||||
|
return (
|
||||||
|
<div className="space-y-3 max-h-72 overflow-auto pr-1">
|
||||||
|
{Array.isArray(activeTab.bullets) && activeTab.bullets.length > 0 && (
|
||||||
|
<ul className="list-disc list-inside text-sm text-gray-700">
|
||||||
|
{activeTab.bullets.map((k: string, i: number) => <li key={i}>{k}</li>)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
{activeTab.content && (
|
||||||
|
<p className="text-sm text-gray-700 whitespace-pre-wrap">{activeTab.content}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Financial Data */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-3">Financial Data</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<Stat label="Current Price" value={fmtCurrency(fd?.current_price)} />
|
||||||
|
<Stat label="Target Price" value={fmtCurrency(fd?.analyst_data?.price_target ?? fd?.target_price)} />
|
||||||
|
<Stat label="Price Change" value={fmtPercent(fd?.price_change)} tone={toneFromNumber(fd?.price_change)} />
|
||||||
|
<Stat label="Price Change %" value={fmtPercent(fd?.price_change_percent)} tone={toneFromNumber(fd?.price_change_percent)} />
|
||||||
|
<Stat label="Market Cap" value={fd?.market_cap ?? '-'} />
|
||||||
|
<Stat label="Enterprise Value" value={fd?.enterprise_value ?? '-'} />
|
||||||
|
<Stat label="Shares Outstanding" value={fd?.shares_outstanding ?? '-'} />
|
||||||
|
<Stat label="Volume" value={fmtNumber(fd?.volume, 0)} />
|
||||||
|
<Stat label="P/E Ratio" value={fmtNumber(fd?.pe_ratio ?? fd?.valuation_ratios?.forward_pe, 2)} />
|
||||||
|
<Stat label="P/S Ratio" value={fmtNumber(fd?.valuation_ratios?.current_ps_ratio, 2)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Technical Indicators */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-3">Technical Indicators</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<Stat label="RSI" value={fmtNumber(ti?.rsi, 2)} tone={toneFromNumber((Number(ti?.rsi) - 50))} />
|
||||||
|
<Stat label="MACD" value={fmtNumber(ti?.macd, 3)} />
|
||||||
|
<Stat label="Signal" value={fmtNumber(ti?.macd_signal, 3)} />
|
||||||
|
<Stat label="SMA 50" value={fmtCurrency((ti?.sma_50 ?? ti?.moving_avg_50), 2)} />
|
||||||
|
<Stat label="SMA 200" value={fmtCurrency((ti?.sma_200 ?? ti?.moving_avg_200), 2)} />
|
||||||
|
<Stat label="MA 20" value={fmtCurrency(ti?.moving_avg_20, 2)} />
|
||||||
|
<Stat label="EMA 10" value={fmtCurrency(ti?.ema_10, 2)} />
|
||||||
|
<Stat label="ATR" value={fmtNumber(ti?.atr, 3)} />
|
||||||
|
<Stat label="Bollinger Upper" value={fmtCurrency(ti?.bollinger_upper, 2)} />
|
||||||
|
<Stat label="Bollinger Lower" value={fmtCurrency(ti?.bollinger_lower, 2)} />
|
||||||
|
<Stat label="Support" value={fmtCurrency(ti?.support_level, 2)} />
|
||||||
|
<Stat label="Resistance" value={fmtCurrency(ti?.resistance_level, 2)} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex flex-wrap gap-2">
|
||||||
|
{chip('Trend SMA 50', trends?.sma_50)}
|
||||||
|
{chip('Trend SMA 200', trends?.sma_200)}
|
||||||
|
{trends?.ema_10 !== undefined && chip('Trend EMA 10', trends?.ema_10)}
|
||||||
|
{trends?.price_action !== undefined && chip('Price', trends?.price_action)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Investment Strategy */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-6 lg:col-span-2">
|
||||||
|
<h2 className="text-lg font-semibold mb-3">Investment Strategy</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<Stat label="Risk Level (SL%)" value={fmtPercent(istrat?.risk_management?.stop_loss_percent ?? md?.risk_level)} />
|
||||||
|
<Stat label="Initial Stop Loss" value={fmtCurrency(istrat?.risk_management?.initial_stop_loss)} />
|
||||||
|
<Stat label="Profit Targets" value={istrat.profit_targets.map((t: any, idx: number) => (
|
||||||
|
<li key={idx} className="flex gap-4">
|
||||||
|
<span className="font-semibold">{fmtCurrency(t?.target_price)}</span>
|
||||||
|
{t?.action && <span className="text-xs text-gray-500">{t.action}</span>}
|
||||||
|
{t?.rationale && <span className="text-xs text-gray-500">{t.rationale}</span>}
|
||||||
|
<hr />
|
||||||
|
</li>
|
||||||
|
))} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -438,8 +605,8 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only render the minimalist Technical Indicators widget for transformed data
|
// Render minimalist dashboard with all keys from the transformed JSON
|
||||||
return <TechnicalIndicatorsOnly data={transformedWithConfig} />;
|
return <MinimalTransformedDashboard data={transformedWithConfig} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TransformedDataAdapter;
|
export default TransformedDataAdapter;
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,21 @@ class TransformedDataService {
|
||||||
return `${symbol}|${date}`;
|
return `${symbol}|${date}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildFileEntry(companySymbol: string, result: any): TransformedAnalysis {
|
||||||
|
const id = this.makeCacheKey(companySymbol, result.date);
|
||||||
|
return {
|
||||||
|
filename: result.filename,
|
||||||
|
date: result.date,
|
||||||
|
modified_at: result.modified_at,
|
||||||
|
file_size: result.file_size,
|
||||||
|
preview: result.preview,
|
||||||
|
error: result.error,
|
||||||
|
symbol: companySymbol,
|
||||||
|
displayName: `${companySymbol} - ${result.date}`,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async getAvailableFiles(): Promise<TransformedAnalysis[]> {
|
async getAvailableFiles(): Promise<TransformedAnalysis[]> {
|
||||||
if (this.cachedFiles) {
|
if (this.cachedFiles) {
|
||||||
return this.cachedFiles;
|
return this.cachedFiles;
|
||||||
|
|
@ -108,38 +123,23 @@ class TransformedDataService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
|
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
|
||||||
const companiesData = companiesResponse.data;
|
const companies = (companiesResponse.data?.companies || [])
|
||||||
const companies = companiesData.companies || [];
|
.filter((c: any) => (c?.transformed_analyses ?? 0) > 0);
|
||||||
|
|
||||||
const files: TransformedAnalysis[] = [];
|
// Fetch each company's transformed results in parallel
|
||||||
|
const companyFetches = companies.map(async (company: any) => {
|
||||||
for (const company of companies) {
|
try {
|
||||||
if (company.transformed_analyses > 0) {
|
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
|
||||||
try {
|
const results = resultsResponse.data?.results || [];
|
||||||
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
|
return results.map((r: any) => this.buildFileEntry(company.symbol, r));
|
||||||
const resultsData = resultsResponse.data;
|
} catch (err) {
|
||||||
const results = resultsData.results || [];
|
console.warn(`Failed to fetch transformed results for ${company.symbol}:`, err);
|
||||||
|
return [] as TransformedAnalysis[];
|
||||||
for (const result of results) {
|
|
||||||
const id = this.makeCacheKey(company.symbol, result.date);
|
|
||||||
files.push({
|
|
||||||
filename: result.filename,
|
|
||||||
date: result.date,
|
|
||||||
modified_at: result.modified_at,
|
|
||||||
file_size: result.file_size,
|
|
||||||
preview: result.preview,
|
|
||||||
error: result.error,
|
|
||||||
symbol: company.symbol,
|
|
||||||
displayName: `${company.symbol} - ${result.date}`,
|
|
||||||
id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to fetch transformed results for ${company.symbol}:`, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const resultsByCompany = await Promise.all(companyFetches);
|
||||||
|
const files = resultsByCompany.flat();
|
||||||
this.cachedFiles = files;
|
this.cachedFiles = files;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Could not load transformed data files:', error);
|
console.warn('Could not load transformed data files:', error);
|
||||||
|
|
@ -164,7 +164,12 @@ class TransformedDataService {
|
||||||
const files = await this.getAvailableFiles();
|
const files = await this.getAvailableFiles();
|
||||||
const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
|
const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
|
||||||
if (!fileEntry) {
|
if (!fileEntry) {
|
||||||
throw new Error(`File not found in index: ${symbol} ${date}`);
|
// Fallback: fetch directly if not in index
|
||||||
|
const response = await axios.get(`${this.baseUrl}/transformed-results/${symbol}/${date}`);
|
||||||
|
const data: TransformedData = response.data.data;
|
||||||
|
this.validateTransformedData(data);
|
||||||
|
this.cachedData.set(cacheKey, data);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.get(`${this.baseUrl}/transformed-results/${fileEntry.symbol}/${date}`);
|
const response = await axios.get(`${this.baseUrl}/transformed-results/${fileEntry.symbol}/${date}`);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify TradingAgents backend API endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def test_backend():
|
||||||
|
"""Test all backend endpoints"""
|
||||||
|
base_url = "http://localhost:8000"
|
||||||
|
|
||||||
|
print("🧪 Testing TradingAgents Backend API")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Test 1: Health check
|
||||||
|
print("\n1. Testing health check...")
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{base_url}/health", timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ Health check passed")
|
||||||
|
print(f" Response: {response.json()}")
|
||||||
|
else:
|
||||||
|
print(f"❌ Health check failed: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print("❌ Backend not running. Please start the backend server first:")
|
||||||
|
print(" cd web_app/backend && python main.py")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Health check error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 2: Companies endpoint
|
||||||
|
print("\n2. Testing companies endpoint...")
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{base_url}/results/companies", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
companies = data.get('companies', [])
|
||||||
|
print(f"✅ Companies endpoint working - Found {len(companies)} companies")
|
||||||
|
for company in companies:
|
||||||
|
print(f" - {company['symbol']}: {company['total_analyses']} analyses, {company.get('transformed_analyses', 0)} transformed")
|
||||||
|
else:
|
||||||
|
print(f"❌ Companies endpoint failed: {response.status_code}")
|
||||||
|
print(f" Response: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Companies endpoint error: {e}")
|
||||||
|
|
||||||
|
# Test 3: Transformed data endpoints
|
||||||
|
print("\n3. Testing transformed data endpoints...")
|
||||||
|
try:
|
||||||
|
# Get companies first
|
||||||
|
response = requests.get(f"{base_url}/results/companies", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
companies = response.json().get('companies', [])
|
||||||
|
|
||||||
|
for company in companies:
|
||||||
|
if company.get('transformed_analyses', 0) > 0:
|
||||||
|
symbol = company['symbol']
|
||||||
|
print(f"\n Testing {symbol} transformed data...")
|
||||||
|
|
||||||
|
# Test company transformed results
|
||||||
|
response = requests.get(f"{base_url}/transformed-results/{symbol}", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
results = response.json().get('results', [])
|
||||||
|
print(f" ✅ {symbol} has {len(results)} transformed analyses")
|
||||||
|
|
||||||
|
# Test specific transformed result
|
||||||
|
if results:
|
||||||
|
first_result = results[0]
|
||||||
|
date = first_result['date']
|
||||||
|
response = requests.get(f"{base_url}/transformed-results/{symbol}/{date}", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(f" ✅ Specific analysis loaded for {symbol} on {date}")
|
||||||
|
|
||||||
|
# Check data structure
|
||||||
|
if 'metadata' in data and 'financial_data' in data:
|
||||||
|
print(f" ✅ Data structure is correct")
|
||||||
|
metadata = data['metadata']
|
||||||
|
print(f" - Company: {metadata.get('company_ticker', 'N/A')}")
|
||||||
|
print(f" - Recommendation: {metadata.get('final_recommendation', 'N/A')}")
|
||||||
|
print(f" - Confidence: {metadata.get('confidence_level', 'N/A')}")
|
||||||
|
print(f" - Current Price: ${metadata.get('current_price', 0)}")
|
||||||
|
else:
|
||||||
|
print(f" ⚠️ Data structure may be incomplete")
|
||||||
|
else:
|
||||||
|
print(f" ❌ Failed to load specific analysis: {response.status_code}")
|
||||||
|
else:
|
||||||
|
print(f" ❌ Failed to load {symbol} transformed data: {response.status_code}")
|
||||||
|
print(f" Response: {response.text}")
|
||||||
|
|
||||||
|
break # Test only first company with transformed data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Transformed data endpoints error: {e}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("🏁 Backend API test completed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_backend()
|
||||||
Loading…
Reference in New Issue