Runn the MVP of the app
This commit is contained in:
parent
09abe2da38
commit
60c0970c2e
|
|
@ -3,7 +3,7 @@ __pycache__/
|
|||
.DS_Store
|
||||
*.csv
|
||||
# src/
|
||||
# eval_results/
|
||||
eval_results/
|
||||
eval_data/
|
||||
*.egg-info/
|
||||
.env
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any, List, Optional
|
||||
from typing import Dict, Any, Optional
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
from datetime import datetime, date
|
||||
from datetime import datetime
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
|
||||
# Import your TradingAgents components
|
||||
|
|
@ -18,6 +16,9 @@ from tradingagents.default_config import DEFAULT_CONFIG
|
|||
|
||||
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
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
|
@ -69,6 +70,8 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
|
|||
|
||||
# Initialize TradingAgents
|
||||
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)
|
||||
|
||||
# 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):
|
||||
"""Start a new trading analysis"""
|
||||
job_id = str(uuid.uuid4())
|
||||
|
||||
|
||||
# Normalize inputs
|
||||
symbol = request.symbol.upper().strip()
|
||||
date = request.date.strip()
|
||||
|
||||
# Validate date format
|
||||
try:
|
||||
datetime.strptime(request.date, "%Y-%m-%d")
|
||||
datetime.strptime(date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
|
||||
|
||||
|
||||
# Initialize job
|
||||
jobs[job_id] = JobStatus(
|
||||
job_id=job_id,
|
||||
status="queued",
|
||||
progress="Analysis queued"
|
||||
)
|
||||
|
||||
|
||||
# Start background task
|
||||
background_tasks.add_task(
|
||||
run_analysis_task,
|
||||
job_id,
|
||||
request.symbol.upper(),
|
||||
request.date,
|
||||
symbol,
|
||||
date,
|
||||
request.config_overrides or {}
|
||||
)
|
||||
|
||||
|
||||
return AnalysisResponse(
|
||||
job_id=job_id,
|
||||
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)
|
||||
|
|
@ -133,7 +140,7 @@ async def get_analysis_status(job_id: str):
|
|||
@app.get("/results/companies")
|
||||
async def get_companies():
|
||||
"""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):
|
||||
return {"companies": []}
|
||||
|
|
@ -176,7 +183,7 @@ async def get_companies():
|
|||
@app.get("/results/{symbol}")
|
||||
async def get_company_results(symbol: str):
|
||||
"""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):
|
||||
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}")
|
||||
async def get_transformed_company_results(symbol: str):
|
||||
"""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):
|
||||
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}")
|
||||
async def get_specific_result(symbol: str, date: str):
|
||||
"""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):
|
||||
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}")
|
||||
async def get_specific_transformed_result(symbol: str, date: str):
|
||||
"""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):
|
||||
raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}")
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ function App() {
|
|||
const runAnalysis = async () => {
|
||||
setIsRunningAnalysis(true);
|
||||
try {
|
||||
const response = await axios.post('/run-analysis', analysisForm);
|
||||
const response = await axios.post('/analysis/start', analysisForm);
|
||||
alert('Analysis completed successfully!');
|
||||
setShowAnalysisModal(false);
|
||||
setAnalysisForm({ symbol: '', date: '' });
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
|
||||
|
||||
// New interface for the transformed JSON structure
|
||||
interface TransformedAnalysisData {
|
||||
|
|
@ -379,58 +378,226 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
|||
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
|
||||
};
|
||||
|
||||
// Minimalist Technical Indicators widget (single-card dashboard)
|
||||
const TechnicalIndicatorsOnly: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
|
||||
const ti = data?.technical_indicators || {} as any;
|
||||
// Minimalist dashboard that shows ALL main sections of transformed JSON
|
||||
const MinimalTransformedDashboard: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
|
||||
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 fmt = (v: any, d = 0) => {
|
||||
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 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 cls = map[String(dir || 'NEUTRAL').toUpperCase()] || map.NEUTRAL;
|
||||
return <span className={`px-2 py-1 rounded text-xs font-medium ${cls}`}>{label}: {dir || 'NEUTRAL'}</span>;
|
||||
const fmtNumber = (v: any, d = 0) => {
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return '-';
|
||||
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 (
|
||||
<div className="min-h-[200px] p-6">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Technical Indicators</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="p-6">
|
||||
<div className="mx-auto max-w-6xl space-y-4">
|
||||
{/* Metadata - Full width */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<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>
|
||||
<p className="text-sm text-gray-500">RSI</p>
|
||||
<p className="text-lg font-semibold">{fmt(ti?.rsi, 2)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">MACD</p>
|
||||
<p className="text-lg font-semibold">{fmt(ti?.macd, 3)}</p>
|
||||
<p className="text-xs text-gray-500">Signal: {fmt(ti?.macd_signal, 3)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">SMA 50</p>
|
||||
<p className="text-lg font-semibold">{fmt(ti?.sma_50, 2)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">SMA 200</p>
|
||||
<p className="text-lg font-semibold">{fmt(ti?.sma_200, 2)}</p>
|
||||
</div>
|
||||
{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 className="flex gap-2 border-b border-gray-200 mb-3">
|
||||
{debateTabs.map(t => (
|
||||
<button
|
||||
key={t.key}
|
||||
onClick={() => setActiveDebateTab(t.key)}
|
||||
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'}`}
|
||||
>{t.label}</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-sm text-gray-800 space-y-2 max-h-64 overflow-auto pr-1">
|
||||
{debateTabs.find(t => t.key === activeDebateTab)?.content?.length ? (
|
||||
<ul className="list-disc list-inside">
|
||||
{(debateTabs.find(t => t.key === activeDebateTab)?.content as any[]).map((c, i) => <li key={i}>{c}</li>)}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-500">No content.</p>
|
||||
)}
|
||||
</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 className="mt-6 flex flex-wrap gap-2">
|
||||
{trendChip('SMA 50', trends?.sma_50)}
|
||||
{trendChip('SMA 200', trends?.sma_200)}
|
||||
{trends?.ema_10 !== undefined && trendChip('EMA 10', trends?.ema_10)}
|
||||
{trends?.price_action !== undefined && trendChip('Price', trends?.price_action)}
|
||||
|
||||
{/* Text Content with sub-tabs */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-3">Reports</h2>
|
||||
{!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>
|
||||
|
|
@ -438,8 +605,8 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
|||
);
|
||||
};
|
||||
|
||||
// Only render the minimalist Technical Indicators widget for transformed data
|
||||
return <TechnicalIndicatorsOnly data={transformedWithConfig} />;
|
||||
// Render minimalist dashboard with all keys from the transformed JSON
|
||||
return <MinimalTransformedDashboard data={transformedWithConfig} />;
|
||||
};
|
||||
|
||||
export default TransformedDataAdapter;
|
||||
|
|
|
|||
|
|
@ -101,6 +101,21 @@ class TransformedDataService {
|
|||
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[]> {
|
||||
if (this.cachedFiles) {
|
||||
return this.cachedFiles;
|
||||
|
|
@ -108,38 +123,23 @@ class TransformedDataService {
|
|||
|
||||
try {
|
||||
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
|
||||
const companiesData = companiesResponse.data;
|
||||
const companies = companiesData.companies || [];
|
||||
|
||||
const files: TransformedAnalysis[] = [];
|
||||
|
||||
for (const company of companies) {
|
||||
if (company.transformed_analyses > 0) {
|
||||
try {
|
||||
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
|
||||
const resultsData = resultsResponse.data;
|
||||
const results = resultsData.results || [];
|
||||
|
||||
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 companies = (companiesResponse.data?.companies || [])
|
||||
.filter((c: any) => (c?.transformed_analyses ?? 0) > 0);
|
||||
|
||||
// Fetch each company's transformed results in parallel
|
||||
const companyFetches = companies.map(async (company: any) => {
|
||||
try {
|
||||
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
|
||||
const results = resultsResponse.data?.results || [];
|
||||
return results.map((r: any) => this.buildFileEntry(company.symbol, r));
|
||||
} catch (err) {
|
||||
console.warn(`Failed to fetch transformed results for ${company.symbol}:`, err);
|
||||
return [] as TransformedAnalysis[];
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const resultsByCompany = await Promise.all(companyFetches);
|
||||
const files = resultsByCompany.flat();
|
||||
this.cachedFiles = files;
|
||||
} catch (error) {
|
||||
console.warn('Could not load transformed data files:', error);
|
||||
|
|
@ -164,7 +164,12 @@ class TransformedDataService {
|
|||
const files = await this.getAvailableFiles();
|
||||
const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
|
||||
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}`);
|
||||
|
|
|
|||
|
|
@ -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