Jjain/eda (#1)
* Add documentation * Update gitignore directories * Add web-app * Rename * Remove dashboard * Add esc functionality * Fix View Results tab * List transformed outputs * Add widget for transformed data * Add a widget for the transformed data * Fix filename issue * Runn the MVP of the app * Update output path to output_data/ * Remove redundant dashboard button * Switch on debug mode * Fix bugs * Update AI models
This commit is contained in:
parent
c5c02b4bff
commit
e82752c0e6
|
|
@ -2,9 +2,10 @@ env/
|
|||
__pycache__/
|
||||
.DS_Store
|
||||
*.csv
|
||||
src/
|
||||
eval_results/
|
||||
# src/
|
||||
output_data/
|
||||
eval_data/
|
||||
*.egg-info/
|
||||
.env
|
||||
trading_agents/*
|
||||
trading_agents/
|
||||
web_app/frontend/node_modules/*
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# List of items to do
|
||||
|
||||
- Replace the embedding model with voyage-finance-2 model as it is more geared towards financial data.
|
||||
- Add a method to include new files and articles and resources to the knowledge base and run the loop of analysis again.
|
||||
- Update the trading prompts to be more specific and to the point.
|
||||
- Research on the new roles of agents to involve and what they will be doing.
|
||||
- Identify places where the human-in-the-loop can be used to improve the system.
|
||||
- Peer comparison
|
||||
- Portfolio management with the Risk management agent
|
||||
|
||||
Questions for financial folks:
|
||||
|
||||
1. Identify new roles for agents that are missing in the company structure that can help add insights into the decision making process.
|
||||
2. Identify the steps in the process where you would want to give manual input and opinions to help guide a discussion between agents.
|
||||
3. Review the prompts for the different agents and identify tasks to add for the agents or improvements.
|
||||
4. List of website that you find helpful and use on a daily basis and maybe some specific tools you use on the website,
|
||||
5. What are some of the decisions that you need help with on a daily basis for which the AI can provide insights and corresponding proof.
|
||||
6. Summary of all the possible sources you interact with while evaluating a stock.
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
# TradingAgents Prompt Inventory
|
||||
|
||||
Table of Contents
|
||||
- [tradingagents/graph/reflection.py](#tradingagentsgraphreflectionpy)
|
||||
- [tradingagents/dataflows/interface.py](#tradingagentsdataflowsinterfacepy)
|
||||
- [create_trader system prompt](#tradingagentsdataflowsinterfacepy)
|
||||
- [create_news_analyst wrapper + role](#tradingagentsdataflowsinterfacepy)
|
||||
- [create_fundamentals_analyst wrapper + role](#tradingagentsdataflowsinterfacepy)
|
||||
- [create_market_analyst wrapper + role](#tradingagentsdataflowsinterfacepy)
|
||||
- [create_social_media_analyst wrapper + role](#tradingagentsdataflowsinterfacepy)
|
||||
- [Web search tool prompts (OpenAI client)](#tradingagentsdataflowsinterfacepy)
|
||||
- [tradingagents/agents/researchers/bull_researcher.py](#tradingagentsagentsresearchersbull_researcherpy)
|
||||
- [tradingagents/agents/researchers/bear_researcher.py](#tradingagentsagentsresearchersbear_researcherpy)
|
||||
- [tradingagents/agents/managers/research_manager.py](#tradingagentsagentsmanagersresearch_managerpy)
|
||||
- [tradingagents/agents/managers/risk_manager.py](#tradingagentsagentsmanagersrisk_managerpy)
|
||||
- [tradingagents/agents/risk_mgmt/aggresive_debator.py](#tradingagentsagentsrisk_mgmtaggresive_debatorpy)
|
||||
- [tradingagents/agents/risk_mgmt/conservative_debator.py](#tradingagentsagentsrisk_mgmtconservative_debatorpy)
|
||||
- [tradingagents/agents/risk_mgmt/neutral_debator.py](#tradingagentsagentsrisk_mgmtneutral_debatorpy)
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/graph/reflection.py
|
||||
|
||||
Lines 15–47: reflection_system_prompt
|
||||
```text
|
||||
You are an expert financial analyst tasked with reviewing trading decisions/analysis and providing a comprehensive, step-by-step analysis.
|
||||
Your goal is to deliver detailed insights into investment decisions and highlight opportunities for improvement, adhering strictly to the following guidelines:
|
||||
|
||||
1. Reasoning:
|
||||
- For each trading decision, determine whether it was correct or incorrect. A correct decision results in an increase in returns, while an incorrect decision does the opposite.
|
||||
- Analyze the contributing factors to each success or mistake. Consider:
|
||||
- Market intelligence.
|
||||
- Technical indicators.
|
||||
- Technical signals.
|
||||
- Price movement analysis.
|
||||
- Overall market data analysis
|
||||
- News analysis.
|
||||
- Social media and sentiment analysis.
|
||||
- Fundamental data analysis.
|
||||
- Weight the importance of each factor in the decision-making process.
|
||||
|
||||
2. Improvement:
|
||||
- For any incorrect decisions, propose revisions to maximize returns.
|
||||
- Provide a detailed list of corrective actions or improvements, including specific recommendations (e.g., changing a decision from HOLD to BUY on a particular date).
|
||||
|
||||
3. Summary:
|
||||
- Summarize the lessons learned from the successes and mistakes.
|
||||
- Highlight how these lessons can be adapted for future trading scenarios and draw connections between similar situations to apply the knowledge gained.
|
||||
|
||||
4. Query:
|
||||
- Extract key insights from the summary into a concise sentence of no more than 1000 tokens.
|
||||
- Ensure the condensed sentence captures the essence of the lessons and reasoning for easy reference.
|
||||
|
||||
Adhere strictly to these instructions, and ensure your output is detailed, accurate, and actionable. You will also be given objective descriptions of the market from a price movements, technical indicator, news, and sentiment perspective to provide more context for your analysis.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/dataflows/interface.py
|
||||
|
||||
Lines 30–36: create_trader system message
|
||||
```text
|
||||
You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situations you traded in and the lessons learned: {past_memory_str}
|
||||
```
|
||||
|
||||
Lines 25–39 (news analyst) wrapper system template
|
||||
```text
|
||||
You are a helpful AI assistant, collaborating with other assistants. Use the provided tools to progress towards answering the question. If you are unable to fully answer, that's OK; another assistant with different tools will help where you left off. Execute what you can to make progress. If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable, prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop. You have access to the following tools: {tool_names}.
|
||||
{system_message}For your reference, the current date is {current_date}. We are looking at the company {ticker}
|
||||
```
|
||||
|
||||
Lines 20–23: news analyst role/system_message
|
||||
```text
|
||||
You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Look at news from EODHD, and finnhub to be comprehensive. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions. Make sure to append a Makrdown table at the end of the report to organize key points in the report, organized and easy to read.
|
||||
```
|
||||
|
||||
Lines 28–41 (fundamentals analyst) wrapper system template
|
||||
```text
|
||||
You are a helpful AI assistant, collaborating with other assistants. Use the provided tools to progress towards answering the question. If you are unable to fully answer, that's OK; another assistant with different tools will help where you left off. Execute what you can to make progress. If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable, prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop. You have access to the following tools: {tool_names}.
|
||||
{system_message}For your reference, the current date is {current_date}. The company we want to look at is {ticker}
|
||||
```
|
||||
|
||||
Lines 23–26: fundamentals analyst role/system_message
|
||||
```text
|
||||
You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, company financial history, insider sentiment and insider transactions to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions. Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.
|
||||
```
|
||||
|
||||
Lines 53–66 (market analyst) wrapper system template
|
||||
```text
|
||||
You are a helpful AI assistant, collaborating with other assistants. Use the provided tools to progress towards answering the question. If you are unable to fully answer, that's OK; another assistant with different tools will help where you left off. Execute what you can to make progress. If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable, prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop. You have access to the following tools: {tool_names}.
|
||||
{system_message}For your reference, the current date is {current_date}. The company we want to look at is {ticker}
|
||||
```
|
||||
|
||||
Lines 24–51: market analyst role/system_message
|
||||
```text
|
||||
You are a trading assistant tasked with analyzing financial markets. Your role is to select the **most relevant indicators** for a given market condition or trading strategy from the following list. The goal is to choose up to **8 indicators** that provide complementary insights without redundancy. Categories and each category's indicators are:
|
||||
|
||||
Moving Averages:
|
||||
- close_50_sma: 50 SMA: A medium-term trend indicator. Usage: Identify trend direction and serve as dynamic support/resistance. Tips: It lags price; combine with faster indicators for timely signals.
|
||||
- close_200_sma: 200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries.
|
||||
- close_10_ema: 10 EMA: A responsive short-term average. Usage: Capture quick shifts in momentum and potential entry points. Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals.
|
||||
|
||||
MACD Related:
|
||||
- macd: MACD: Computes momentum via differences of EMAs. Usage: Look for crossovers and divergence as signals of trend changes. Tips: Confirm with other indicators in low-volatility or sideways markets.
|
||||
- macds: MACD Signal: An EMA smoothing of the MACD line. Usage: Use crossovers with the MACD line to trigger trades. Tips: Should be part of a broader strategy to avoid false positives.
|
||||
- macdh: MACD Histogram: Shows the gap between the MACD line and its signal. Usage: Visualize momentum strength and spot divergence early. Tips: Can be volatile; complement with additional filters in fast-moving markets.
|
||||
|
||||
Momentum Indicators:
|
||||
- rsi: RSI: Measures momentum to flag overbought/oversold conditions. Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis.
|
||||
|
||||
Volatility Indicators:
|
||||
- boll: Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. Usage: Acts as a dynamic benchmark for price movement. Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals.
|
||||
- boll_ub: Bollinger Upper Band: Typically 2 standard deviations above the middle line. Usage: Signals potential overbought conditions and breakout zones. Tips: Confirm signals with other tools; prices may ride the band in strong trends.
|
||||
- boll_lb: Bollinger Lower Band: Typically 2 standard deviations below the middle line. Usage: Indicates potential oversold conditions. Tips: Use additional analysis to avoid false reversal signals.
|
||||
- atr: ATR: Averages true range to measure volatility. Usage: Set stop-loss levels and adjust position sizes based on current market volatility. Tips: It's a reactive measure, so use it as part of a broader risk management strategy.
|
||||
|
||||
Volume-Based Indicators:
|
||||
- vwma: VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses.
|
||||
|
||||
Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_YFin_data first to retrieve the CSV that is needed to generate indicators. Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions. Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.
|
||||
```
|
||||
|
||||
Lines 24–36 (social media analyst) wrapper system template
|
||||
```text
|
||||
You are a helpful AI assistant, collaborating with other assistants. Use the provided tools to progress towards answering the question. If you are unable to fully answer, that's OK; another assistant with different tools will help where you left off. Execute what you can to make progress. If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable, prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop. You have access to the following tools: {tool_names}.
|
||||
{system_message}For your reference, the current date is {current_date}. The current company we want to analyze is {ticker}
|
||||
```
|
||||
|
||||
Lines 19–22: social media analyst role/system_message
|
||||
```text
|
||||
You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions. Make sure to append a Makrdown table at the end of the report to organize key points in the report, organized and easy to read.
|
||||
```
|
||||
|
||||
Lines 705–737: get_stock_news_openai system input_text
|
||||
```text
|
||||
Can you search Social Media for {ticker} from 7 days before {curr_date} to {curr_date}? Make sure you only get the data posted during that period.
|
||||
```
|
||||
|
||||
Lines 740–772: get_global_news_openai system input_text
|
||||
```text
|
||||
Can you search global or macroeconomics news from 7 days before {curr_date} to {curr_date} that would be informative for trading purposes? Make sure you only get the data posted during that period.
|
||||
```
|
||||
|
||||
Lines 775–807: get_fundamentals_openai system input_text
|
||||
```text
|
||||
Can you search Fundamental for discussions on {ticker} during of the month before {curr_date} to the month of {curr_date}. Make sure you only get the data posted during that period. List as a table, with PE/PS/Cash flow/ etc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/researchers/bull_researcher.py
|
||||
|
||||
Lines 25–43: Bull Analyst prompt
|
||||
```text
|
||||
You are a Bull Analyst advocating for investing in the stock. Your task is to build a strong, evidence-based case emphasizing growth potential, competitive advantages, and positive market indicators. Leverage the provided research and data to address concerns and counter bearish arguments effectively.
|
||||
|
||||
Key points to focus on:
|
||||
- Growth Potential: Highlight the company's market opportunities, revenue projections, and scalability.
|
||||
- Competitive Advantages: Emphasize factors like unique products, strong branding, or dominant market positioning.
|
||||
- Positive Indicators: Use financial health, industry trends, and recent positive news as evidence.
|
||||
- Bear Counterpoints: Critically analyze the bear argument with specific data and sound reasoning, addressing concerns thoroughly and showing why the bull perspective holds stronger merit.
|
||||
- Engagement: Present your argument in a conversational style, engaging directly with the bear analyst's points and debating effectively rather than just listing data.
|
||||
|
||||
Resources available:
|
||||
Market research report: {market_research_report}
|
||||
Social media sentiment report: {sentiment_report}
|
||||
Latest world affairs news: {news_report}
|
||||
Company fundamentals report: {fundamentals_report}
|
||||
Conversation history of the debate: {history}
|
||||
Last bear argument: {current_response}
|
||||
Reflections from similar situations and lessons learned: {past_memory_str}
|
||||
Use this information to deliver a compelling bull argument, refute the bear's concerns, and engage in a dynamic debate that demonstrates the strengths of the bull position. You must also address reflections and learn from lessons and mistakes you made in the past.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/researchers/bear_researcher.py
|
||||
|
||||
Lines 25–45: Bear Analyst prompt
|
||||
```text
|
||||
You are a Bear Analyst making the case against investing in the stock. Your goal is to present a well-reasoned argument emphasizing risks, challenges, and negative indicators. Leverage the provided research and data to highlight potential downsides and counter bullish arguments effectively.
|
||||
|
||||
Key points to focus on:
|
||||
|
||||
- Risks and Challenges: Highlight factors like market saturation, financial instability, or macroeconomic threats that could hinder the stock's performance.
|
||||
- Competitive Weaknesses: Emphasize vulnerabilities such as weaker market positioning, declining innovation, or threats from competitors.
|
||||
- Negative Indicators: Use evidence from financial data, market trends, or recent adverse news to support your position.
|
||||
- Bull Counterpoints: Critically analyze the bull argument with specific data and sound reasoning, exposing weaknesses or over-optimistic assumptions.
|
||||
- Engagement: Present your argument in a conversational style, directly engaging with the bull analyst's points and debating effectively rather than simply listing facts.
|
||||
|
||||
Resources available:
|
||||
|
||||
Market research report: {market_research_report}
|
||||
Social media sentiment report: {sentiment_report}
|
||||
Latest world affairs news: {news_report}
|
||||
Company fundamentals report: {fundamentals_report}
|
||||
Conversation history of the debate: {history}
|
||||
Last bull argument: {current_response}
|
||||
Reflections from similar situations and lessons learned: {past_memory_str}
|
||||
Use this information to deliver a compelling bear argument, refute the bull's claims, and engage in a dynamic debate that demonstrates the risks and weaknesses of investing in the stock. You must also address reflections and learn from lessons and mistakes you made in the past.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/managers/research_manager.py
|
||||
|
||||
Lines 22–38: Research Manager / Judge prompt
|
||||
```text
|
||||
As the portfolio manager and debate facilitator, your role is to critically evaluate this round of debate and make a definitive decision: align with the bear analyst, the bull analyst, or choose Hold only if it is strongly justified based on the arguments presented.
|
||||
|
||||
Summarize the key points from both sides concisely, focusing on the most compelling evidence or reasoning. Your recommendation—Buy, Sell, or Hold—must be clear and actionable. Avoid defaulting to Hold simply because both sides have valid points; commit to a stance grounded in the debate's strongest arguments.
|
||||
|
||||
Additionally, develop a detailed investment plan for the trader. This should include:
|
||||
|
||||
Your Recommendation: A decisive stance supported by the most convincing arguments.
|
||||
Rationale: An explanation of why these arguments lead to your conclusion.
|
||||
Strategic Actions: Concrete steps for implementing the recommendation.
|
||||
Take into account your past mistakes on similar situations. Use these insights to refine your decision-making and ensure you are learning and improving. Present your analysis conversationally, as if speaking naturally, without special formatting.
|
||||
|
||||
Here are your past reflections on mistakes:
|
||||
"{past_memory_str}"
|
||||
|
||||
Here is the debate:
|
||||
Debate History:
|
||||
{history}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/managers/risk_manager.py
|
||||
|
||||
Lines 25–44: Risk Manager Judge prompt
|
||||
```text
|
||||
As the Risk Management Judge and Debate Facilitator, your goal is to evaluate the debate between three risk analysts—Risky, Neutral, and Safe/Conservative—and determine the best course of action for the trader. Your decision must result in a clear recommendation: Buy, Sell, or Hold. Choose Hold only if strongly justified by specific arguments, not as a fallback when all sides seem valid. Strive for clarity and decisiveness.
|
||||
|
||||
Guidelines for Decision-Making:
|
||||
1. **Summarize Key Arguments**: Extract the strongest points from each analyst, focusing on relevance to the context.
|
||||
2. **Provide Rationale**: Support your recommendation with direct quotes and counterarguments from the debate.
|
||||
3. **Refine the Trader's Plan**: Start with the trader's original plan, **{trader_plan}**, and adjust it based on the analysts' insights.
|
||||
4. **Learn from Past Mistakes**: Use lessons from **{past_memory_str}** to address prior misjudgments and improve the decision you are making now to make sure you don't make a wrong BUY/SELL/HOLD call that loses money.
|
||||
|
||||
Deliverables:
|
||||
- A clear and actionable recommendation: Buy, Sell, or Hold.
|
||||
- Detailed reasoning anchored in the debate and past reflections.
|
||||
|
||||
---
|
||||
|
||||
**Analysts Debate History:**
|
||||
{history}
|
||||
|
||||
---
|
||||
|
||||
Focus on actionable insights and continuous improvement. Build on past lessons, critically evaluate all perspectives, and ensure each decision advances better outcomes.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/risk_mgmt/aggresive_debator.py
|
||||
|
||||
Lines 21–33: Risky Analyst prompt
|
||||
```text
|
||||
As the Risky Risk Analyst, your role is to actively champion high-reward, high-risk opportunities, emphasizing bold strategies and competitive advantages. When evaluating the trader's decision or plan, focus intently on the potential upside, growth potential, and innovative benefits—even when these come with elevated risk. Use the provided market data and sentiment analysis to strengthen your arguments and challenge the opposing views. Specifically, respond directly to each point made by the conservative and neutral analysts, countering with data-driven rebuttals and persuasive reasoning. Highlight where their caution might miss critical opportunities or where their assumptions may be overly conservative. Here is the trader's decision:
|
||||
|
||||
{trader_decision}
|
||||
|
||||
Your task is to create a compelling case for the trader's decision by questioning and critiquing the conservative and neutral stances to demonstrate why your high-reward perspective offers the best path forward. Incorporate insights from the following sources into your arguments:
|
||||
|
||||
Market Research Report: {market_research_report}
|
||||
Social Media Sentiment Report: {sentiment_report}
|
||||
Latest World Affairs Report: {news_report}
|
||||
Company Fundamentals Report: {fundamentals_report}
|
||||
Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_safe_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
|
||||
|
||||
Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/risk_mgmt/conservative_debator.py
|
||||
|
||||
Lines 22–34: Safe/Conservative Analyst prompt
|
||||
```text
|
||||
As the Safe/Conservative Risk Analyst, your primary objective is to protect assets, minimize volatility, and ensure steady, reliable growth. You prioritize stability, security, and risk mitigation, carefully assessing potential losses, economic downturns, and market volatility. When evaluating the trader's decision or plan, critically examine high-risk elements, pointing out where the decision may expose the firm to undue risk and where more cautious alternatives could secure long-term gains. Here is the trader's decision:
|
||||
|
||||
{trader_decision}
|
||||
|
||||
Your task is to actively counter the arguments of the Risky and Neutral Analysts, highlighting where their views may overlook potential threats or fail to prioritize sustainability. Respond directly to their points, drawing from the following data sources to build a convincing case for a low-risk approach adjustment to the trader's decision:
|
||||
|
||||
Market Research Report: {market_research_report}
|
||||
Social Media Sentiment Report: {sentiment_report}
|
||||
Latest World Affairs Report: {news_report}
|
||||
Company Fundamentals Report: {fundamentals_report}
|
||||
Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
|
||||
|
||||
Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## tradingagents/agents/risk_mgmt/neutral_debator.py
|
||||
|
||||
Lines 21–33: Neutral Analyst prompt
|
||||
```text
|
||||
As the Neutral Risk Analyst, your role is to provide a balanced perspective, weighing both the potential benefits and risks of the trader's decision or plan. You prioritize a well-rounded approach, evaluating the upsides and downsides while factoring in broader market trends, potential economic shifts, and diversification strategies.Here is the trader's decision:
|
||||
|
||||
{trader_decision}
|
||||
|
||||
Your task is to challenge both the Risky and Safe Analysts, pointing out where each perspective may be overly optimistic or overly cautious. Use insights from the following data sources to support a moderate, sustainable strategy to adjust the trader's decision:
|
||||
|
||||
Market Research Report: {market_research_report}
|
||||
Social Media Sentiment Report: {sentiment_report}
|
||||
Latest World Affairs Report: {news_report}
|
||||
Company Fundamentals Report: {fundamentals_report}
|
||||
Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the safe analyst: {current_safe_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
|
||||
|
||||
Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.
|
||||
10
main.py
10
main.py
|
|
@ -3,10 +3,10 @@ from tradingagents.default_config import DEFAULT_CONFIG
|
|||
|
||||
# Create a custom config
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["llm_provider"] = "google" # Use a different model
|
||||
config["backend_url"] = "https://generativelanguage.googleapis.com/v1" # Use a different backend
|
||||
config["deep_think_llm"] = "gemini-2.0-flash" # Use a different model
|
||||
config["quick_think_llm"] = "gemini-2.0-flash" # Use a different model
|
||||
config["llm_provider"] = "openai" # Use a different model
|
||||
config["backend_url"] = "https://api.openai.com/v1" # Use a different backend
|
||||
config["deep_think_llm"] = "o4-mini" # Use a different model
|
||||
config["quick_think_llm"] = "gpt-4o-mini" # Use a different model
|
||||
config["max_debate_rounds"] = 1 # Increase debate rounds
|
||||
config["online_tools"] = True # Increase debate rounds
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ config["online_tools"] = True # Increase debate rounds
|
|||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
|
||||
# forward propagate
|
||||
_, decision = ta.propagate("NVDA", "2024-05-10")
|
||||
_, decision = ta.propagate("NVDA", "2024-08-01")
|
||||
print(decision)
|
||||
|
||||
# Memorize mistakes and reflect
|
||||
|
|
|
|||
|
|
@ -19,8 +19,14 @@ requests
|
|||
tqdm
|
||||
pytz
|
||||
redis
|
||||
chainlit
|
||||
rich
|
||||
questionary
|
||||
langchain_anthropic
|
||||
langchain-google-genai
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
pydantic
|
||||
python-multipart
|
||||
python-jose[cryptography]
|
||||
passlib[bcrypt]
|
||||
python-dotenv
|
||||
|
|
@ -10,15 +10,16 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"id": "b6517669",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"sys.path.insert(0, os.path.abspath('..'))\n"
|
||||
"sys.path.insert(0, os.path.abspath('..'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -31,6 +32,765 @@
|
|||
"import tradingagents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"id": "0f0abc48",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"import re\n",
|
||||
"from datetime import datetime\n",
|
||||
"from typing import Dict, Any, List, Optional\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"import openai\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class TransformationConfig:\n",
|
||||
" \"\"\"Configuration for the data transformation agent\"\"\"\n",
|
||||
" openai_api_key: str\n",
|
||||
" model: str = \"gpt-4o-mini\"\n",
|
||||
" eval_results_path: str = \"scripts/eval_results/AVAH/TradingAgentsStrategy_logs\"\n",
|
||||
" output_path: str = \"scripts/eval_results/AVAH/TradingAgentsStrategy_transformed_logs\"\n",
|
||||
" backend_url: str = \"https://api.openai.com/v1\"\n",
|
||||
" \n",
|
||||
"class DataTransformationAgent:\n",
|
||||
" \"\"\"Agent that transforms TradingAgents output into widget-friendly JSON format\"\"\"\n",
|
||||
" \n",
|
||||
" def __init__(self, config: TransformationConfig):\n",
|
||||
" self.config = config\n",
|
||||
" self.client = openai.OpenAI(\n",
|
||||
" api_key=config.openai_api_key,\n",
|
||||
" base_url=config.backend_url\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Ensure output directory exists\n",
|
||||
" os.makedirs(self.config.output_path, exist_ok=True)\n",
|
||||
" \n",
|
||||
" def get_transformation_prompt(self) -> str:\n",
|
||||
" \"\"\"Returns the comprehensive transformation prompt\"\"\"\n",
|
||||
" return \"\"\"\n",
|
||||
"You are a data transformation specialist. Take the provided investment analysis JSON and restructure it into a widget-friendly format that separates visual data from text content for easy frontend consumption.\n",
|
||||
"\n",
|
||||
"## Input Format\n",
|
||||
"The input JSON contains investment analysis data with the following structure:\n",
|
||||
"- `company_of_interest`: Stock ticker\n",
|
||||
"- `trade_date`: Analysis date\n",
|
||||
"- `market_report`: Technical analysis text\n",
|
||||
"- `sentiment_report`: Company sentiment analysis text\n",
|
||||
"- `news_report`: Macroeconomic news text\n",
|
||||
"- `fundamentals_report`: Financial metrics and company data text\n",
|
||||
"- `investment_debate_state`: Object containing bull/bear/neutral arguments\n",
|
||||
"- `risk_debate_state`: Object containing risk analysis discussions\n",
|
||||
"- `investment_plan`: Final investment strategy text\n",
|
||||
"- `trader_investment_decision`: Final decision rationale text\n",
|
||||
"- `final_trade_decision`: Ultimate trade recommendation text\n",
|
||||
"\n",
|
||||
"## Output Requirements\n",
|
||||
"Transform the input into a structured JSON with the following sections:\n",
|
||||
"\n",
|
||||
"### 1. Widget Data Structure\n",
|
||||
"```json\n",
|
||||
"{\n",
|
||||
" \"metadata\": {\n",
|
||||
" \"company_ticker\": \"string\",\n",
|
||||
" \"company_name\": \"string\", \n",
|
||||
" \"analysis_date\": \"YYYY-MM-DD\",\n",
|
||||
" \"final_recommendation\": \"BUY|SELL|HOLD\",\n",
|
||||
" \"confidence_level\": \"HIGH|MEDIUM|LOW\"\n",
|
||||
" },\n",
|
||||
" \n",
|
||||
" \"financial_data\": {\n",
|
||||
" \"current_price\": number,\n",
|
||||
" \"price_change\": number,\n",
|
||||
" \"price_change_percent\": number,\n",
|
||||
" \"market_cap\": \"string\",\n",
|
||||
" \"enterprise_value\": \"string\",\n",
|
||||
" \"shares_outstanding\": \"string\",\n",
|
||||
" \"trading_range\": {\n",
|
||||
" \"high\": number,\n",
|
||||
" \"low\": number,\n",
|
||||
" \"open\": number\n",
|
||||
" },\n",
|
||||
" \"volume\": number,\n",
|
||||
" \"valuation_ratios\": {\n",
|
||||
" \"current_ps_ratio\": number,\n",
|
||||
" \"fair_value_ps_ratio\": number,\n",
|
||||
" \"forward_pe\": number,\n",
|
||||
" \"forward_ps\": number,\n",
|
||||
" \"forward_pcf\": number,\n",
|
||||
" \"forward_pocf\": number\n",
|
||||
" },\n",
|
||||
" \"ownership\": {\n",
|
||||
" \"insider_percent\": number,\n",
|
||||
" \"institutional_percent\": number\n",
|
||||
" },\n",
|
||||
" \"analyst_data\": {\n",
|
||||
" \"consensus_rating\": \"string\",\n",
|
||||
" \"price_target\": number,\n",
|
||||
" \"forecast_price\": number\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
"\n",
|
||||
" \"technical_indicators\": {\n",
|
||||
" \"sma_50\": number,\n",
|
||||
" \"sma_200\": number,\n",
|
||||
" \"ema_10\": number,\n",
|
||||
" \"macd\": number,ticker\n",
|
||||
" \"macd_signal\": number,\n",
|
||||
" \"rsi\": number,\n",
|
||||
" \"atr\": number,\n",
|
||||
" \"trend_directions\": {\n",
|
||||
" \"sma_50\": \"BULLISH|BEARISH|NEUTRAL\",\n",
|
||||
" \"sma_200\": \"BULLISH|BEARISH|NEUTRAL\",\n",
|
||||
" \"ema_10\": \"BULLISH|BEARISH|NEUTRAL\",\n",
|
||||
" \"macd\": \"BULLISH|BEARISH|NEUTRAL\",\n",
|
||||
" \"rsi_condition\": \"OVERSOLD|OVERBOUGHT|NEUTRAL\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
"\n",
|
||||
" \"investment_strategy\": {\n",
|
||||
" \"position_sizing\": {\n",
|
||||
" \"total_allocation_percent\": \"string\",\n",
|
||||
" \"entry_strategy\": \"string\",\n",
|
||||
" \"tranche_1_percent\": \"string\",\n",
|
||||
" \"tranche_2_percent\": \"string\"\n",
|
||||
" },\n",
|
||||
" \"risk_management\": {\n",
|
||||
" \"initial_stop_loss\": number,\n",
|
||||
" \"stop_loss_percent\": number,\n",
|
||||
" \"breakeven_strategy\": \"string\"\n",
|
||||
" },\n",
|
||||
" \"profit_targets\": [\n",
|
||||
" {\n",
|
||||
" \"target_price\": number,\n",
|
||||
" \"action\": \"string\",\n",
|
||||
" \"rationale\": \"string\"\n",
|
||||
" }\n",
|
||||
" ],\n",
|
||||
" \"monitoring_points\": [\n",
|
||||
" \"string\"\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
"\n",
|
||||
" \"debate_summary\": {\n",
|
||||
" \"bull_key_points\": [\n",
|
||||
" \"string\"\n",
|
||||
" ],\n",
|
||||
" \"bear_key_points\": [\n",
|
||||
" \"string\"\n",
|
||||
" ],\n",
|
||||
" \"neutral_perspective\": \"string\",\n",
|
||||
" \"final_decision_rationale\": \"string\"\n",
|
||||
" },\n",
|
||||
"\n",
|
||||
" \"text_content\": {\n",
|
||||
" \"market_report\": {\n",
|
||||
" \"title\": \"Technical Analysis Report\",\n",
|
||||
" \"content\": \"string\",\n",
|
||||
" \"key_takeaways\": [\n",
|
||||
" \"string\"\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" \"sentiment_report\": {\n",
|
||||
" \"title\": \"Company Sentiment Analysis\", \n",
|
||||
" \"content\": \"string\",\n",
|
||||
" \"recent_developments\": [\n",
|
||||
" \"string\"\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" \"fundamentals_report\": {\n",
|
||||
" \"title\": \"Fundamental Analysis\",\n",
|
||||
" \"content\": \"string\",\n",
|
||||
" \"financial_highlights\": [\n",
|
||||
" \"string\"\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" \"news_report\": {\n",
|
||||
" \"title\": \"Macroeconomic Context\",\n",
|
||||
" \"content\": \"string\",\n",
|
||||
" \"key_developments\": [\n",
|
||||
" {\n",
|
||||
" \"date\": \"string\",\n",
|
||||
" \"event\": \"string\",\n",
|
||||
" \"impact\": \"string\"\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" \"investment_plan_full\": {\n",
|
||||
" \"title\": \"Complete Investment Strategy\",\n",
|
||||
" \"content\": \"string\"\n",
|
||||
" },\n",
|
||||
" \"debate_transcripts\": {\n",
|
||||
" \"bull_analysis\": \"string\",\n",
|
||||
" \"bear_analysis\": \"string\",\n",
|
||||
" \"neutral_analysis\": \"string\",\n",
|
||||
" \"risk_discussion\": \"string\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
"\n",
|
||||
" \"widgets_config\": {\n",
|
||||
" \"charts_needed\": [\n",
|
||||
" {\n",
|
||||
" \"type\": \"price_chart\",\n",
|
||||
" \"data_source\": \"financial_data.current_price\",\n",
|
||||
" \"timeframe\": \"30_days\"\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"type\": \"technical_indicators\",\n",
|
||||
" \"data_source\": \"technical_indicators\"\n",
|
||||
" }\n",
|
||||
" ],\n",
|
||||
" \"text_widgets\": [\n",
|
||||
" {\n",
|
||||
" \"type\": \"expandable_report\",\n",
|
||||
" \"title\": \"Technical Analysis\",\n",
|
||||
" \"content_source\": \"text_content.market_report\"\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"## Extraction Instructions\n",
|
||||
"\n",
|
||||
"1. **Parse Financial Metrics**: Extract all numerical values from the fundamentals_report, including current price, ratios, market cap, etc.\n",
|
||||
"\n",
|
||||
"2. **Extract Technical Data**: Pull technical indicator values and trend directions from the market_report text\n",
|
||||
"\n",
|
||||
"3. **Summarize Debates**: Create concise bullet points from the lengthy bull/bear arguments, focusing on key investment themes\n",
|
||||
"\n",
|
||||
"4. **Structure Investment Plan**: Break down the investment strategy into actionable components (sizing, entry price,stops, targets, time horizon)\n",
|
||||
"\n",
|
||||
"5. **Organize Text Content**: Preserve full text reports while also extracting key highlights for quick reference\n",
|
||||
"\n",
|
||||
"6. **Identify Key Dates**: Extract important dates like earnings calls, trade dates, and catalyst events\n",
|
||||
"\n",
|
||||
"7. **Classify Sentiment**: Determine overall sentiment scores and confidence levels based on the analysis\n",
|
||||
"\n",
|
||||
"## Data Validation\n",
|
||||
"- Ensure all numerical values are properly typed (numbers vs strings)\n",
|
||||
"- Validate date formats are consistent\n",
|
||||
"- Check that all required fields are populated\n",
|
||||
"- Verify that text content is properly escaped for JSON\n",
|
||||
"\n",
|
||||
"## Output Optimization\n",
|
||||
"- Structure data for easy consumption by frontend frameworks (React, Vue, Angular)\n",
|
||||
"- Separate frequently-accessed data (current price, recommendation) from detailed reports\n",
|
||||
"- Include metadata for widget configuration and rendering preferences\n",
|
||||
"- Provide fallback values for any missing data points\n",
|
||||
"\n",
|
||||
"Transform the input JSON following this structure to create a comprehensive, widget-ready dataset that maintains all original information while making it easily accessible for dashboard creation.\n",
|
||||
"\n",
|
||||
"IMPORTANT: Return ONLY the transformed JSON, no additional text or explanations.\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
" def extract_numerical_value(self, text: str, pattern: str, default: float = 0.0) -> float:\n",
|
||||
" \"\"\"Extract numerical values from text using regex patterns\"\"\"\n",
|
||||
" try:\n",
|
||||
" match = re.search(pattern, text, re.IGNORECASE)\n",
|
||||
" if match:\n",
|
||||
" value_str = match.group(1).replace(',', '').replace('$', '').replace('%', '')\n",
|
||||
" return float(value_str)\n",
|
||||
" except (ValueError, AttributeError):\n",
|
||||
" pass\n",
|
||||
" return default\n",
|
||||
"\n",
|
||||
" def transform_single_file(self, input_data: Dict[str, Any]) -> Dict[str, Any]:\n",
|
||||
" \"\"\"Transform a single TradingAgents JSON file using LLM\"\"\"\n",
|
||||
" try:\n",
|
||||
" # Prepare the input data as a JSON string\n",
|
||||
" input_json = json.dumps(input_data, indent=2)\n",
|
||||
" \n",
|
||||
" # Create the prompt with the input data\n",
|
||||
" full_prompt = f\"{self.get_transformation_prompt()}\\n\\nInput JSON to transform:\\n{input_json}\"\n",
|
||||
" \n",
|
||||
" # Call OpenAI API\n",
|
||||
" response = self.client.chat.completions.create(\n",
|
||||
" model=self.config.model,\n",
|
||||
" messages=[\n",
|
||||
" {\"role\": \"system\", \"content\": \"You are a data transformation specialist. Transform the provided JSON exactly as specified.\"},\n",
|
||||
" {\"role\": \"user\", \"content\": full_prompt}\n",
|
||||
" ],\n",
|
||||
" temperature=0.1,\n",
|
||||
" max_tokens=16384\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Parse the response\n",
|
||||
" transformed_json_str = response.choices[0].message.content.strip()\n",
|
||||
" \n",
|
||||
" # Clean up the response (remove any markdown formatting)\n",
|
||||
" if transformed_json_str.startswith('```json'):\n",
|
||||
" transformed_json_str = transformed_json_str[7:]\n",
|
||||
" if transformed_json_str.endswith('```'):\n",
|
||||
" transformed_json_str = transformed_json_str[:-3]\n",
|
||||
" \n",
|
||||
" transformed_data = json.loads(transformed_json_str)\n",
|
||||
" \n",
|
||||
" # Add fallback values if transformation missed anything\n",
|
||||
" self._add_fallback_values(transformed_data, input_data)\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Error transforming data: {e}\")\n",
|
||||
" # Return a basic fallback structure\n",
|
||||
" transformed_data = self._create_fallback_structure(input_data)\n",
|
||||
" \n",
|
||||
" return transformed_data\n",
|
||||
" \n",
|
||||
" def _add_fallback_values(self, transformed_data: Dict[str, Any], original_data: Dict[str, Any]):\n",
|
||||
" \"\"\"Add fallback values for any missing required fields\"\"\"\n",
|
||||
" \n",
|
||||
" # Ensure metadata exists\n",
|
||||
" if 'metadata' not in transformed_data:\n",
|
||||
" transformed_data['metadata'] = {}\n",
|
||||
" \n",
|
||||
" metadata = transformed_data['metadata']\n",
|
||||
" if 'company_ticker' not in metadata:\n",
|
||||
" metadata['company_ticker'] = original_data.get('company_of_interest', 'UNKNOWN')\n",
|
||||
" if 'analysis_date' not in metadata:\n",
|
||||
" metadata['analysis_date'] = original_data.get('trade_date', datetime.now().strftime('%Y-%m-%d'))\n",
|
||||
" if 'final_recommendation' not in metadata:\n",
|
||||
" metadata['final_recommendation'] = 'HOLD'\n",
|
||||
" if 'confidence_level' not in metadata:\n",
|
||||
" metadata['confidence_level'] = 'MEDIUM'\n",
|
||||
"\n",
|
||||
" # Ensure all required sections exist\n",
|
||||
" required_sections = [\n",
|
||||
" 'financial_data', 'technical_indicators', 'investment_strategy',\n",
|
||||
" 'debate_summary', 'text_content', 'widgets_config'\n",
|
||||
" ]\n",
|
||||
" \n",
|
||||
" for section in required_sections:\n",
|
||||
" if section not in transformed_data:\n",
|
||||
" transformed_data[section] = {}\n",
|
||||
"\n",
|
||||
" def _create_fallback_structure(self, original_data: Dict[str, Any]) -> Dict[str, Any]:\n",
|
||||
" \"\"\"Create a basic fallback structure when transformation fails\"\"\"\n",
|
||||
" return {\n",
|
||||
" \"metadata\": {\n",
|
||||
" \"company_ticker\": original_data.get('company_of_interest', 'UNKNOWN'),\n",
|
||||
" \"company_name\": original_data.get('company_of_interest', 'Unknown Company'),\n",
|
||||
" \"analysis_date\": original_data.get('trade_date', datetime.now().strftime('%Y-%m-%d')),\n",
|
||||
" \"final_recommendation\": \"HOLD\",\n",
|
||||
" \"confidence_level\": \"LOW\"\n",
|
||||
" },\n",
|
||||
" \"financial_data\": {\n",
|
||||
" \"current_price\": 0.0,\n",
|
||||
" \"price_change\": 0.0,\n",
|
||||
" \"price_change_percent\": 0.0,\n",
|
||||
" \"market_cap\": \"N/A\",\n",
|
||||
" \"enterprise_value\": \"N/A\",\n",
|
||||
" \"shares_outstanding\": \"N/A\",\n",
|
||||
" \"trading_range\": {\"high\": 0.0, \"low\": 0.0, \"open\": 0.0},\n",
|
||||
" \"volume\": 0,\n",
|
||||
" \"valuation_ratios\": {\n",
|
||||
" \"current_ps_ratio\": 0.0,\n",
|
||||
" \"fair_value_ps_ratio\": 0.0,\n",
|
||||
" \"forward_pe\": 0.0,\n",
|
||||
" \"forward_ps\": 0.0,\n",
|
||||
" \"forward_pcf\": 0.0,\n",
|
||||
" \"forward_pocf\": 0.0\n",
|
||||
" },\n",
|
||||
" \"ownership\": {\"insider_percent\": 0.0, \"institutional_percent\": 0.0},\n",
|
||||
" \"analyst_data\": {\n",
|
||||
" \"consensus_rating\": \"N/A\",\n",
|
||||
" \"price_target\": 0.0,\n",
|
||||
" \"forecast_price\": 0.0\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"technical_indicators\": {\n",
|
||||
" \"sma_50\": 0.0,\n",
|
||||
" \"sma_200\": 0.0,\n",
|
||||
" \"ema_10\": 0.0,\n",
|
||||
" \"macd\": 0.0,\n",
|
||||
" \"macd_signal\": 0.0,\n",
|
||||
" \"rsi\": 50.0,\n",
|
||||
" \"atr\": 0.0,\n",
|
||||
" \"trend_directions\": {\n",
|
||||
" \"sma_50\": \"NEUTRAL\",\n",
|
||||
" \"sma_200\": \"NEUTRAL\",\n",
|
||||
" \"ema_10\": \"NEUTRAL\",\n",
|
||||
" \"macd\": \"NEUTRAL\",\n",
|
||||
" \"rsi_condition\": \"NEUTRAL\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"investment_strategy\": {\n",
|
||||
" \"position_sizing\": {\n",
|
||||
" \"total_allocation_percent\": \"0%\",\n",
|
||||
" \"entry_strategy\": \"N/A\",\n",
|
||||
" \"tranche_1_percent\": \"0%\",\n",
|
||||
" \"tranche_2_percent\": \"0%\"\n",
|
||||
" },\n",
|
||||
" \"risk_management\": {\n",
|
||||
" \"initial_stop_loss\": 0.0,\n",
|
||||
" \"stop_loss_percent\": 0.0,\n",
|
||||
" \"breakeven_strategy\": \"N/A\"\n",
|
||||
" },\n",
|
||||
" \"profit_targets\": [],\n",
|
||||
" \"monitoring_points\": []\n",
|
||||
" },\n",
|
||||
" \"debate_summary\": {\n",
|
||||
" \"bull_key_points\": [],\n",
|
||||
" \"bear_key_points\": [],\n",
|
||||
" \"neutral_perspective\": \"No analysis available\",\n",
|
||||
" \"final_decision_rationale\": \"No decision rationale available\"\n",
|
||||
" },\n",
|
||||
" \"text_content\": {\n",
|
||||
" \"market_report\": {\n",
|
||||
" \"title\": \"Technical Analysis Report\",\n",
|
||||
" \"content\": original_data.get('market_report', 'No market report available'),\n",
|
||||
" \"key_takeaways\": []\n",
|
||||
" },\n",
|
||||
" \"sentiment_report\": {\n",
|
||||
" \"title\": \"Company Sentiment Analysis\",\n",
|
||||
" \"content\": original_data.get('sentiment_report', 'No sentiment report available'),\n",
|
||||
" \"recent_developments\": []\n",
|
||||
" },\n",
|
||||
" \"fundamentals_report\": {\n",
|
||||
" \"title\": \"Fundamental Analysis\",\n",
|
||||
" \"content\": original_data.get('fundamentals_report', 'No fundamentals report available'),\n",
|
||||
" \"financial_highlights\": []\n",
|
||||
" },\n",
|
||||
" \"news_report\": {\n",
|
||||
" \"title\": \"Macroeconomic Context\",\n",
|
||||
" \"content\": original_data.get('news_report', 'No news report available'),\n",
|
||||
" \"key_developments\": []\n",
|
||||
" },\n",
|
||||
" \"investment_plan_full\": {\n",
|
||||
" \"title\": \"Complete Investment Strategy\",\n",
|
||||
" \"content\": original_data.get('investment_plan', 'No investment plan available')\n",
|
||||
" },\n",
|
||||
" \"debate_transcripts\": {\n",
|
||||
" \"bull_analysis\": \"\",\n",
|
||||
" \"bear_analysis\": \"\",\n",
|
||||
" \"neutral_analysis\": \"\",\n",
|
||||
" \"risk_discussion\": \"\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"widgets_config\": {\n",
|
||||
" \"charts_needed\": [\n",
|
||||
" {\"type\": \"price_chart\", \"data_source\": \"financial_data.current_price\", \"timeframe\": \"30_days\"},\n",
|
||||
" {\"type\": \"technical_indicators\", \"data_source\": \"technical_indicators\"}\n",
|
||||
" ],\n",
|
||||
" \"text_widgets\": [\n",
|
||||
" {\"type\": \"expandable_report\", \"title\": \"Technical Analysis\", \"content_source\": \"text_content.market_report\"}\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" def process_all_files(self) -> Dict[str, List[str]]:\n",
|
||||
" \"\"\"Process all JSON files in the eval_results directory\"\"\"\n",
|
||||
" results = {\"success\": [], \"failed\": []}\n",
|
||||
" \n",
|
||||
" eval_results_path = Path(self.config.eval_results_path)\n",
|
||||
" \n",
|
||||
" if not eval_results_path.exists():\n",
|
||||
" print(f\"Eval results path does not exist: {eval_results_path}\")\n",
|
||||
" return results\n",
|
||||
" \n",
|
||||
" # Process each company directory\n",
|
||||
" for company_dir in eval_results_path.iterdir():\n",
|
||||
" if not company_dir.is_dir():\n",
|
||||
" continue\n",
|
||||
" \n",
|
||||
" company_ticker = company_dir.name\n",
|
||||
" logs_dir = company_dir / \"TradingAgentsStrategy_logs\"\n",
|
||||
" transformed_dir = company_dir / \"TradingAgentsStrategy_transformed_logs\"\n",
|
||||
" transformed_dir.mkdir(parents=True, exist_ok=True)\n",
|
||||
" \n",
|
||||
" # Process each JSON file in the logs directory\n",
|
||||
" for json_file in logs_dir.glob(\"*.json\"):\n",
|
||||
" try:\n",
|
||||
" print(f\"Processing {json_file}\")\n",
|
||||
" \n",
|
||||
" # Process the file\n",
|
||||
" success = self.process_single_file(str(json_file), str(transformed_dir))\n",
|
||||
" \n",
|
||||
" if success:\n",
|
||||
" results[\"success\"].append(str(json_file.name))\n",
|
||||
" print(f\"Successfully transformed and saved: {json_file.name}\")\n",
|
||||
" else:\n",
|
||||
" results[\"failed\"].append(str(json_file))\n",
|
||||
" print(f\"Failed to process {json_file}\")\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Failed to process {json_file}: {e}\")\n",
|
||||
" results[\"failed\"].append(str(json_file))\n",
|
||||
" \n",
|
||||
" return results\n",
|
||||
"\n",
|
||||
" def process_single_file(self, input_file_path: str, output_file_path: str = None) -> bool:\n",
|
||||
" \"\"\"Process a single JSON file\"\"\"\n",
|
||||
" try:\n",
|
||||
" input_path = Path(input_file_path)\n",
|
||||
" \n",
|
||||
" if not input_path.exists():\n",
|
||||
" print(f\"Input file does not exist: {input_path}\")\n",
|
||||
" return False\n",
|
||||
" \n",
|
||||
" # Load the original data\n",
|
||||
" with open(input_path, 'r') as f:\n",
|
||||
" original_data = json.load(f)\n",
|
||||
" \n",
|
||||
" # Transform the data\n",
|
||||
" transformed_data = self.transform_single_file(original_data)\n",
|
||||
" \n",
|
||||
" # Determine output path\n",
|
||||
" if output_file_path is None:\n",
|
||||
" output_file_path = Path(self.config.output_path) / f\"{input_path.stem}_transformed.json\"\n",
|
||||
" else:\n",
|
||||
" output_file_path = Path(output_file_path) / f\"{input_path.stem}_transformed.json\"\n",
|
||||
"\n",
|
||||
" \n",
|
||||
" # Save the transformed data\n",
|
||||
" with open(output_file_path, 'w') as f:\n",
|
||||
" json.dump(transformed_data, f, indent=2)\n",
|
||||
" \n",
|
||||
" print(f\"Successfully transformed and saved: {output_file_path}\")\n",
|
||||
" return True\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Failed to process {input_file_path}: {e}\")\n",
|
||||
" return False\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "ed3a84b1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ.get(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"id": "636bea83",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'full_states_log_2025-07-26.json'"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"str(Path(\"eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-07-26.json\").name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6e138b44",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Processing eval_results/PLTR/TradingAgentsStrategy_logs/full_states_log_2025-08-01.json\n",
|
||||
"Successfully transformed and saved: eval_results/PLTR/TradingAgentsStrategy_transformed_logs/full_states_log_2025-08-01_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-08-01.json\n",
|
||||
"Processing eval_results/RDDT/TradingAgentsStrategy_logs/full_states_log_2025-08-01.json\n",
|
||||
"Successfully transformed and saved: eval_results/RDDT/TradingAgentsStrategy_transformed_logs/full_states_log_2025-08-01_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-08-01.json\n",
|
||||
"Processing eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-07-26.json\n",
|
||||
"Successfully transformed and saved: eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-07-26_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-07-26.json\n",
|
||||
"Processing eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-08-05.json\n",
|
||||
"Successfully transformed and saved: eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-08-05_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-08-05.json\n",
|
||||
"Processing eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-08-06.json\n",
|
||||
"Successfully transformed and saved: eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-08-06_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-08-06.json\n",
|
||||
"Processing eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-08-07.json\n",
|
||||
"Successfully transformed and saved: eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-08-07_transformed.json\n",
|
||||
"Successfully transformed and saved: full_states_log_2025-08-07.json\n",
|
||||
"\n",
|
||||
"Processing completed:\n",
|
||||
"Success: 6 files\n",
|
||||
"Failed: 0 files\n",
|
||||
"\n",
|
||||
"Successfully processed files:\n",
|
||||
" - full_states_log_2025-08-01.json\n",
|
||||
" - full_states_log_2025-08-01.json\n",
|
||||
" - full_states_log_2025-07-26.json\n",
|
||||
" - full_states_log_2025-08-05.json\n",
|
||||
" - full_states_log_2025-08-06.json\n",
|
||||
" - full_states_log_2025-08-07.json\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"\n",
|
||||
"config = TransformationConfig(\n",
|
||||
" openai_api_key=\"\",\n",
|
||||
" eval_results_path=\"eval_results/\",\n",
|
||||
" # output_path=\"eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-07-26.json\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Create agent\n",
|
||||
"agent = DataTransformationAgent(config)\n",
|
||||
"\n",
|
||||
"if False:\n",
|
||||
" # Process single file\n",
|
||||
" success = agent.process_single_file(\"eval_results/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-07-26.json\", \"eval_results/AVAH/TradingAgentsStrategy_transformed_logs/\")\n",
|
||||
" if success:\n",
|
||||
" print(\"Single file processing completed successfully\")\n",
|
||||
" else:\n",
|
||||
" print(\"Single file processing failed\")\n",
|
||||
"else:\n",
|
||||
" # Process all files\n",
|
||||
" results = agent.process_all_files()\n",
|
||||
" print(f\"\\nProcessing completed:\")\n",
|
||||
" print(f\"Success: {len(results['success'])} files\")\n",
|
||||
" print(f\"Failed: {len(results['failed'])} files\")\n",
|
||||
" \n",
|
||||
" if results['success']:\n",
|
||||
" print(\"\\nSuccessfully processed files:\")\n",
|
||||
" for file_path in results['success']:\n",
|
||||
" print(f\" - {file_path}\")\n",
|
||||
" \n",
|
||||
" if results['failed']:\n",
|
||||
" print(\"\\nFailed to process files:\")\n",
|
||||
" for file_path in results['failed']:\n",
|
||||
" print(f\" - {file_path}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "1dc59a16",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"with open(f\"eval_results/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-07-26.json\") as f:\n",
|
||||
" input = json.load(f)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8d772497",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'metadata': {'company_ticker': 'AVAH',\n",
|
||||
" 'company_name': 'Aveanna Healthcare Holdings Inc.',\n",
|
||||
" 'analysis_date': '2025-08-06',\n",
|
||||
" 'final_recommendation': 'HOLD',\n",
|
||||
" 'confidence_level': 'MEDIUM'},\n",
|
||||
" 'financial_data': {'current_price': 5.81,\n",
|
||||
" 'price_change': 1.93,\n",
|
||||
" 'price_change_percent': 49.49,\n",
|
||||
" 'market_cap': '814.80 million',\n",
|
||||
" 'enterprise_value': '2.24 billion',\n",
|
||||
" 'shares_outstanding': '206.28 million',\n",
|
||||
" 'trading_range': {'high': 6.19, 'low': 4.4, 'open': 5.35},\n",
|
||||
" 'volume': 14408713,\n",
|
||||
" 'valuation_ratios': {'current_ps_ratio': 0.54,\n",
|
||||
" 'fair_value_ps_ratio': 0.8,\n",
|
||||
" 'forward_pe': 145.24,\n",
|
||||
" 'forward_ps': 0.5,\n",
|
||||
" 'forward_pcf': 49.02,\n",
|
||||
" 'forward_pocf': 23.39},\n",
|
||||
" 'ownership': {'insider_percent': 2.74, 'institutional_percent': 20.26},\n",
|
||||
" 'analyst_data': {'consensus_rating': 'HOLD',\n",
|
||||
" 'price_target': 5.0,\n",
|
||||
" 'forecast_price': 5.25}},\n",
|
||||
" 'technical_indicators': {'sma_50': 4.61,\n",
|
||||
" 'sma_200': 4.88,\n",
|
||||
" 'ema_10': 4.25,\n",
|
||||
" 'macd': -0.0427,\n",
|
||||
" 'macd_signal': -0.1917,\n",
|
||||
" 'rsi': 77.09,\n",
|
||||
" 'atr': 0.334,\n",
|
||||
" 'trend_directions': {'sma_50': 'BEARISH',\n",
|
||||
" 'sma_200': 'BEARISH',\n",
|
||||
" 'ema_10': 'BEARISH',\n",
|
||||
" 'macd': 'BEARISH',\n",
|
||||
" 'rsi_condition': 'OVERBOUGHT'}},\n",
|
||||
" 'investment_strategy': {'position_sizing': {'total_allocation_percent': '3-5%',\n",
|
||||
" 'entry_strategy': 'Staggered entry around $3.75-$3.80',\n",
|
||||
" 'tranche_1_percent': '50%',\n",
|
||||
" 'tranche_2_percent': '50%'},\n",
|
||||
" 'risk_management': {'initial_stop_loss': 3.0,\n",
|
||||
" 'stop_loss_percent': 20,\n",
|
||||
" 'breakeven_strategy': 'Adjust stop to breakeven after first tranche fills'},\n",
|
||||
" 'profit_targets': [{'target_price': 5.0,\n",
|
||||
" 'action': 'Sell 25-50%',\n",
|
||||
" 'rationale': 'Reassess margin trends'},\n",
|
||||
" {'target_price': 7.5,\n",
|
||||
" 'action': 'Hold for longer-term gains',\n",
|
||||
" 'rationale': 'Reflects a move to 0.8x P/S based on projected FY24 revenues'}],\n",
|
||||
" 'monitoring_points': ['Government reimbursement rate details',\n",
|
||||
" 'Patient census/margin trends',\n",
|
||||
" 'Competitive landscape and near-term cost pressures']},\n",
|
||||
" 'debate_summary': {'bull_key_points': ['Strong revenue growth of 16.8% in Q1',\n",
|
||||
" 'Aging population driving demand for home healthcare',\n",
|
||||
" 'Integrated service offerings create competitive advantages',\n",
|
||||
" 'Potential for recovery indicated by oversold conditions'],\n",
|
||||
" 'bear_key_points': ['High P/E ratio of 145.24 raises valuation concerns',\n",
|
||||
" 'Negative book value and high debt levels pose risks',\n",
|
||||
" 'Market volatility and competition could impact margins',\n",
|
||||
" 'RSI indicates overbought conditions, suggesting a potential pullback'],\n",
|
||||
" 'neutral_perspective': 'A balanced approach is needed, recognizing both growth potential and inherent risks.',\n",
|
||||
" 'final_decision_rationale': 'While growth prospects are promising, the risks associated with high leverage and market volatility necessitate a cautious approach.'},\n",
|
||||
" 'text_content': {'market_report': {'title': 'Technical Analysis Report',\n",
|
||||
" 'content': 'AVAH has seen significant fluctuations in price, with key indicators suggesting a bearish trend. The stock closed at $3.96 on July 25, indicating a decrease from earlier highs. The analysis of various indicators shows potential for a reversal, but caution is warranted.',\n",
|
||||
" 'key_takeaways': ['Current price trends indicate bearish sentiment.',\n",
|
||||
" 'Technical indicators suggest potential for a price correction.']},\n",
|
||||
" 'sentiment_report': {'title': 'Company Sentiment Analysis',\n",
|
||||
" 'content': \"Aveanna's upcoming earnings report is anticipated to be a pivotal moment for the stock. Recent analyst activity suggests a price target of $5.00, indicating potential upside.\",\n",
|
||||
" 'recent_developments': ['Earnings release scheduled for August 7, 2025.',\n",
|
||||
" 'Analyst price target set at $5.00.']},\n",
|
||||
" 'fundamentals_report': {'title': 'Fundamental Analysis',\n",
|
||||
" 'content': 'Aveanna Healthcare Holdings Inc. has shown a mixed financial picture with significant revenue growth but concerning debt levels. Investors should monitor cash flow and debt management closely.',\n",
|
||||
" 'financial_highlights': ['Current price at $5.81 with a 49.49% increase.',\n",
|
||||
" 'P/S ratio of 0.54 indicates potential undervaluation.']},\n",
|
||||
" 'news_report': {'title': 'Macroeconomic Context',\n",
|
||||
" 'content': 'Recent macroeconomic trends, including trade agreements and Federal Reserve policies, have created a favorable backdrop for healthcare investments. However, rising tariffs and inflation remain concerns.',\n",
|
||||
" 'key_developments': [{'date': '2025-08-07',\n",
|
||||
" 'event': 'Earnings release',\n",
|
||||
" 'impact': 'Potential for increased volatility.'}]},\n",
|
||||
" 'investment_plan_full': {'title': 'Complete Investment Strategy',\n",
|
||||
" 'content': 'The investment strategy for AVAH includes a staggered entry approach, risk management through stop-loss orders, and monitoring key performance indicators to ensure informed decision-making.'},\n",
|
||||
" 'debate_transcripts': {'bull_analysis': 'The bullish case emphasizes strong revenue growth and favorable market conditions, suggesting that AVAH is well-positioned for future success.',\n",
|
||||
" 'bear_analysis': 'The bearish perspective highlights significant risks associated with high debt levels and market volatility, urging caution among investors.',\n",
|
||||
" 'neutral_analysis': 'A balanced view recognizes the potential for growth while also addressing the inherent risks, advocating for a measured investment approach.',\n",
|
||||
" 'risk_discussion': 'The discussion around risk emphasizes the importance of managing exposure and being prepared for market fluctuations.'}},\n",
|
||||
" 'widgets_config': {'charts_needed': [{'type': 'price_chart',\n",
|
||||
" 'data_source': 'financial_data.current_price',\n",
|
||||
" 'timeframe': '30_days'},\n",
|
||||
" {'type': 'technical_indicators', 'data_source': 'technical_indicators'}],\n",
|
||||
" 'text_widgets': [{'type': 'expandable_report',\n",
|
||||
" 'title': 'Technical Analysis',\n",
|
||||
" 'content_source': 'text_content.market_report'}]}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"transform_single_file(input_data):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,10 +39,10 @@ deactivate nondestructive
|
|||
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
export VIRTUAL_ENV=$(cygpath /home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents)
|
||||
export VIRTUAL_ENV=$(cygpath '/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents')
|
||||
else
|
||||
# use the path as-is
|
||||
export VIRTUAL_ENV=/home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents
|
||||
export VIRTUAL_ENV='/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents'
|
||||
fi
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
|||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV /home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents
|
||||
setenv VIRTUAL_ENV '/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents'
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ end
|
|||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV /home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents
|
||||
set -gx VIRTUAL_ENV '/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents'
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#!/home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents/bin/python3
|
||||
#!/bin/sh
|
||||
'''exec' "/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents/bin/python3" "$0" "$@"
|
||||
' '''
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#!/home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents/bin/python3
|
||||
#!/bin/sh
|
||||
'''exec' "/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents/bin/python3" "$0" "$@"
|
||||
' '''
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#!/home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents/bin/python3
|
||||
#!/bin/sh
|
||||
'''exec' "/home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents/bin/python3" "$0" "$@"
|
||||
' '''
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
../../../bin/pip,sha256=C51rGrtgO4AgRp2MGgjc_BxaIZKH3CvY7JPq-aWB3Fw,285
|
||||
../../../bin/pip3,sha256=C51rGrtgO4AgRp2MGgjc_BxaIZKH3CvY7JPq-aWB3Fw,285
|
||||
../../../bin/pip3.12,sha256=C51rGrtgO4AgRp2MGgjc_BxaIZKH3CvY7JPq-aWB3Fw,285
|
||||
../../../bin/pip,sha256=Ky8idrfwpeeXRfnwglVFl8_2QVpormnnt0YzocW3VuY,316
|
||||
../../../bin/pip3,sha256=Ky8idrfwpeeXRfnwglVFl8_2QVpormnnt0YzocW3VuY,316
|
||||
../../../bin/pip3.12,sha256=Ky8idrfwpeeXRfnwglVFl8_2QVpormnnt0YzocW3VuY,316
|
||||
pip-24.0.dist-info/AUTHORS.txt,sha256=SwXm4nkwRkmtnO1ZY-dLy7EPeoQNXMNLby5CN3GlNhY,10388
|
||||
pip-24.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pip-24.0.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ home = /usr/bin
|
|||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python3 -m venv /home/brabus61/.local/share/Trash/files/TradingAgents/trading_agents
|
||||
command = /usr/bin/python3 -m venv /home/brabus61/Desktop/Github Repos/TradingAgents/trading_agents
|
||||
|
|
|
|||
|
|
@ -0,0 +1,572 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional
|
||||
from dataclasses import dataclass
|
||||
import openai
|
||||
from pathlib import Path
|
||||
|
||||
@dataclass
|
||||
class TransformationConfig:
|
||||
"""Configuration for the data transformation agent"""
|
||||
openai_api_key: str = os.environ.get("OPENAI_API_KEY")
|
||||
model: str = "gpt-4o-mini"
|
||||
eval_results_path: str = "../output_data/AVAH/TradingAgentsStrategy_logs"
|
||||
output_path: str = "../output_data/AVAH/TradingAgentsStrategy_transformed_logs"
|
||||
backend_url: str = "https://api.openai.com/v1"
|
||||
|
||||
class DataTransformationAgent:
|
||||
"""Agent that transforms TradingAgents output into widget-friendly JSON format"""
|
||||
|
||||
def __init__(self, config: TransformationConfig):
|
||||
self.config = config
|
||||
self.client = openai.OpenAI(
|
||||
api_key=config.openai_api_key,
|
||||
base_url=config.backend_url
|
||||
)
|
||||
|
||||
# Ensure output directory exists
|
||||
os.makedirs(self.config.output_path, exist_ok=True)
|
||||
|
||||
def get_transformation_prompt(self) -> str:
|
||||
"""Returns the comprehensive transformation prompt"""
|
||||
return """
|
||||
You are a data transformation specialist. Take the provided investment analysis JSON and restructure it into a widget-friendly format that separates visual data from text content for easy frontend consumption.
|
||||
|
||||
## Input Format
|
||||
The input JSON contains investment analysis data with the following structure:
|
||||
- `company_of_interest`: Stock ticker
|
||||
- `trade_date`: Analysis date
|
||||
- `market_report`: Technical analysis text
|
||||
- `sentiment_report`: Company sentiment analysis text
|
||||
- `news_report`: Macroeconomic news text
|
||||
- `fundamentals_report`: Financial metrics and company data text
|
||||
- `investment_debate_state`: Object containing bull/bear/neutral arguments
|
||||
- `risk_debate_state`: Object containing risk analysis discussions
|
||||
- `investment_plan`: Final investment strategy text
|
||||
- `trader_investment_decision`: Final decision rationale text
|
||||
- `final_trade_decision`: Ultimate trade recommendation text
|
||||
|
||||
## Output Requirements
|
||||
Transform the input into a structured JSON with the following sections:
|
||||
|
||||
### 1. Widget Data Structure
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"company_ticker": "string",
|
||||
"company_name": "string",
|
||||
"analysis_date": "YYYY-MM-DD",
|
||||
"final_recommendation": "BUY|SELL|HOLD",
|
||||
"confidence_level": "HIGH|MEDIUM|LOW"
|
||||
},
|
||||
|
||||
"financial_data": {
|
||||
"current_price": number,
|
||||
"price_change": number,
|
||||
"price_change_percent": number,
|
||||
"market_cap": "string",
|
||||
"enterprise_value": "string",
|
||||
"shares_outstanding": "string",
|
||||
"trading_range": {
|
||||
"high": number,
|
||||
"low": number,
|
||||
"open": number
|
||||
},
|
||||
"volume": number,
|
||||
"valuation_ratios": {
|
||||
"current_ps_ratio": number,
|
||||
"fair_value_ps_ratio": number,
|
||||
"forward_pe": number,
|
||||
"forward_ps": number,
|
||||
"forward_pcf": number,
|
||||
"forward_pocf": number
|
||||
},
|
||||
"ownership": {
|
||||
"insider_percent": number,
|
||||
"institutional_percent": number
|
||||
},
|
||||
"analyst_data": {
|
||||
"consensus_rating": "string",
|
||||
"price_target": number,
|
||||
"forecast_price": number
|
||||
}
|
||||
},
|
||||
|
||||
"technical_indicators": {
|
||||
"sma_50": number,
|
||||
"sma_200": number,
|
||||
"ema_10": number,
|
||||
"macd": number,
|
||||
"macd_signal": number,
|
||||
"rsi": number,
|
||||
"atr": number,
|
||||
"trend_directions": {
|
||||
"sma_50": "BULLISH|BEARISH|NEUTRAL",
|
||||
"sma_200": "BULLISH|BEARISH|NEUTRAL",
|
||||
"ema_10": "BULLISH|BEARISH|NEUTRAL",
|
||||
"macd": "BULLISH|BEARISH|NEUTRAL",
|
||||
"rsi_condition": "OVERSOLD|OVERBOUGHT|NEUTRAL"
|
||||
}
|
||||
},
|
||||
|
||||
"investment_strategy": {
|
||||
"position_sizing": {
|
||||
"total_allocation_percent": "string",
|
||||
"entry_strategy": "string",
|
||||
"tranche_1_percent": "string",
|
||||
"tranche_2_percent": "string"
|
||||
},
|
||||
"risk_management": {
|
||||
"initial_stop_loss": number,
|
||||
"stop_loss_percent": number,
|
||||
"breakeven_strategy": "string"
|
||||
},
|
||||
"profit_targets": [
|
||||
{
|
||||
"target_price": number,
|
||||
"action": "string",
|
||||
"rationale": "string"
|
||||
}
|
||||
],
|
||||
"monitoring_points": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
|
||||
"debate_summary": {
|
||||
"bull_key_points": [
|
||||
"string"
|
||||
],
|
||||
"bear_key_points": [
|
||||
"string"
|
||||
],
|
||||
"neutral_perspective": "string",
|
||||
"final_decision_rationale": "string"
|
||||
},
|
||||
|
||||
"text_content": {
|
||||
"market_report": {
|
||||
"title": "Technical Analysis Report",
|
||||
"content": "string",
|
||||
"key_takeaways": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"sentiment_report": {
|
||||
"title": "Company Sentiment Analysis",
|
||||
"content": "string",
|
||||
"recent_developments": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"fundamentals_report": {
|
||||
"title": "Fundamental Analysis",
|
||||
"content": "string",
|
||||
"financial_highlights": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"news_report": {
|
||||
"title": "Macroeconomic Context",
|
||||
"content": "string",
|
||||
"key_developments": [
|
||||
{
|
||||
"date": "string",
|
||||
"event": "string",
|
||||
"impact": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"investment_plan_full": {
|
||||
"title": "Complete Investment Strategy",
|
||||
"content": "string"
|
||||
},
|
||||
"debate_transcripts": {
|
||||
"bull_analysis": "string",
|
||||
"bear_analysis": "string",
|
||||
"neutral_analysis": "string",
|
||||
"risk_discussion": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"widgets_config": {
|
||||
"charts_needed": [
|
||||
{
|
||||
"type": "price_chart",
|
||||
"data_source": "financial_data.current_price",
|
||||
"timeframe": "30_days"
|
||||
},
|
||||
{
|
||||
"type": "technical_indicators",
|
||||
"data_source": "technical_indicators"
|
||||
}
|
||||
],
|
||||
"text_widgets": [
|
||||
{
|
||||
"type": "expandable_report",
|
||||
"title": "Technical Analysis",
|
||||
"content_source": "text_content.market_report"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Extraction Instructions
|
||||
|
||||
1. **Parse Financial Metrics**: Extract all numerical values from the fundamentals_report, including current price, ratios, market cap, etc.
|
||||
|
||||
2. **Extract Technical Data**: Pull technical indicator values and trend directions from the market_report text
|
||||
|
||||
3. **Summarize Debates**: Create concise bullet points from the lengthy bull/bear arguments, focusing on key investment themes
|
||||
|
||||
4. **Structure Investment Plan**: Break down the investment strategy into actionable components (sizing, entry price,stops, targets, time horizon)
|
||||
|
||||
5. **Organize Text Content**: Preserve full text reports while also extracting key highlights for quick reference
|
||||
|
||||
6. **Identify Key Dates**: Extract important dates like earnings calls, trade dates, and catalyst events
|
||||
|
||||
7. **Classify Sentiment**: Determine overall sentiment scores and confidence levels based on the analysis
|
||||
|
||||
## Data Validation
|
||||
- Ensure all numerical values are properly typed (numbers vs strings)
|
||||
- Validate date formats are consistent
|
||||
- Check that all required fields are populated
|
||||
- Verify that text content is properly escaped for JSON
|
||||
|
||||
## Output Optimization
|
||||
- Structure data for easy consumption by frontend frameworks (React, Vue, Angular)
|
||||
- Separate frequently-accessed data (current price, recommendation) from detailed reports
|
||||
- Include metadata for widget configuration and rendering preferences
|
||||
- Provide fallback values for any missing data points
|
||||
|
||||
Transform the input JSON following this structure to create a comprehensive, widget-ready dataset that maintains all original information while making it easily accessible for dashboard creation.
|
||||
|
||||
IMPORTANT: Return ONLY the transformed JSON, no additional text or explanations.
|
||||
"""
|
||||
|
||||
def extract_numerical_value(self, text: str, pattern: str, default: float = 0.0) -> float:
|
||||
"""Extract numerical values from text using regex patterns"""
|
||||
try:
|
||||
match = re.search(pattern, text, re.IGNORECASE)
|
||||
if match:
|
||||
value_str = match.group(1).replace(',', '').replace('$', '').replace('%', '')
|
||||
return float(value_str)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
return default
|
||||
|
||||
def transform_single_file(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Transform a single TradingAgents JSON file using LLM"""
|
||||
try:
|
||||
# Prepare the input data as a JSON string
|
||||
input_json = json.dumps(input_data, indent=2)
|
||||
|
||||
# Create the prompt with the input data
|
||||
full_prompt = f"{self.get_transformation_prompt()}\n\nInput JSON to transform:\n{input_json}"
|
||||
|
||||
# Call OpenAI API
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.config.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a data transformation specialist. Transform the provided JSON exactly as specified."},
|
||||
{"role": "user", "content": full_prompt}
|
||||
],
|
||||
temperature=0.1,
|
||||
max_tokens=16384
|
||||
)
|
||||
|
||||
# Parse the response
|
||||
transformed_json_str = response.choices[0].message.content.strip()
|
||||
|
||||
# Clean up the response (remove any markdown formatting)
|
||||
if transformed_json_str.startswith('```json'):
|
||||
transformed_json_str = transformed_json_str[7:]
|
||||
if transformed_json_str.endswith('```'):
|
||||
transformed_json_str = transformed_json_str[:-3]
|
||||
|
||||
transformed_data = json.loads(transformed_json_str)
|
||||
|
||||
# Add fallback values if transformation missed anything
|
||||
self._add_fallback_values(transformed_data, input_data)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error transforming data: {e}")
|
||||
# Return a basic fallback structure
|
||||
transformed_data = self._create_fallback_structure(input_data)
|
||||
|
||||
return transformed_data
|
||||
|
||||
def _add_fallback_values(self, transformed_data: Dict[str, Any], original_data: Dict[str, Any]):
|
||||
"""Add fallback values for any missing required fields"""
|
||||
|
||||
# Ensure metadata exists
|
||||
if 'metadata' not in transformed_data:
|
||||
transformed_data['metadata'] = {}
|
||||
|
||||
metadata = transformed_data['metadata']
|
||||
if 'company_ticker' not in metadata:
|
||||
metadata['company_ticker'] = original_data.get('company_of_interest', 'UNKNOWN')
|
||||
if 'analysis_date' not in metadata:
|
||||
metadata['analysis_date'] = original_data.get('trade_date', datetime.now().strftime('%Y-%m-%d'))
|
||||
if 'final_recommendation' not in metadata:
|
||||
metadata['final_recommendation'] = 'HOLD'
|
||||
if 'confidence_level' not in metadata:
|
||||
metadata['confidence_level'] = 'MEDIUM'
|
||||
|
||||
# Ensure all required sections exist
|
||||
required_sections = [
|
||||
'financial_data', 'technical_indicators', 'investment_strategy',
|
||||
'debate_summary', 'text_content', 'widgets_config'
|
||||
]
|
||||
|
||||
for section in required_sections:
|
||||
if section not in transformed_data:
|
||||
transformed_data[section] = {}
|
||||
|
||||
def _create_fallback_structure(self, original_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Create a basic fallback structure when transformation fails"""
|
||||
return {
|
||||
"metadata": {
|
||||
"company_ticker": original_data.get('company_of_interest', 'UNKNOWN'),
|
||||
"company_name": original_data.get('company_of_interest', 'Unknown Company'),
|
||||
"analysis_date": original_data.get('trade_date', datetime.now().strftime('%Y-%m-%d')),
|
||||
"final_recommendation": "HOLD",
|
||||
"confidence_level": "LOW"
|
||||
},
|
||||
"financial_data": {
|
||||
"current_price": 0.0,
|
||||
"price_change": 0.0,
|
||||
"price_change_percent": 0.0,
|
||||
"market_cap": "N/A",
|
||||
"enterprise_value": "N/A",
|
||||
"shares_outstanding": "N/A",
|
||||
"trading_range": {"high": 0.0, "low": 0.0, "open": 0.0},
|
||||
"volume": 0,
|
||||
"valuation_ratios": {
|
||||
"current_ps_ratio": 0.0,
|
||||
"fair_value_ps_ratio": 0.0,
|
||||
"forward_pe": 0.0,
|
||||
"forward_ps": 0.0,
|
||||
"forward_pcf": 0.0,
|
||||
"forward_pocf": 0.0
|
||||
},
|
||||
"ownership": {"insider_percent": 0.0, "institutional_percent": 0.0},
|
||||
"analyst_data": {
|
||||
"consensus_rating": "N/A",
|
||||
"price_target": 0.0,
|
||||
"forecast_price": 0.0
|
||||
}
|
||||
},
|
||||
"technical_indicators": {
|
||||
"sma_50": 0.0,
|
||||
"sma_200": 0.0,
|
||||
"ema_10": 0.0,
|
||||
"macd": 0.0,
|
||||
"macd_signal": 0.0,
|
||||
"rsi": 50.0,
|
||||
"atr": 0.0,
|
||||
"trend_directions": {
|
||||
"sma_50": "NEUTRAL",
|
||||
"sma_200": "NEUTRAL",
|
||||
"ema_10": "NEUTRAL",
|
||||
"macd": "NEUTRAL",
|
||||
"rsi_condition": "NEUTRAL"
|
||||
}
|
||||
},
|
||||
"investment_strategy": {
|
||||
"position_sizing": {
|
||||
"total_allocation_percent": "0%",
|
||||
"entry_strategy": "N/A",
|
||||
"tranche_1_percent": "0%",
|
||||
"tranche_2_percent": "0%"
|
||||
},
|
||||
"risk_management": {
|
||||
"initial_stop_loss": 0.0,
|
||||
"stop_loss_percent": 0.0,
|
||||
"breakeven_strategy": "N/A"
|
||||
},
|
||||
"profit_targets": [],
|
||||
"monitoring_points": []
|
||||
},
|
||||
"debate_summary": {
|
||||
"bull_key_points": [],
|
||||
"bear_key_points": [],
|
||||
"neutral_perspective": "No analysis available",
|
||||
"final_decision_rationale": "No decision rationale available"
|
||||
},
|
||||
"text_content": {
|
||||
"market_report": {
|
||||
"title": "Technical Analysis Report",
|
||||
"content": original_data.get('market_report', 'No market report available'),
|
||||
"key_takeaways": []
|
||||
},
|
||||
"sentiment_report": {
|
||||
"title": "Company Sentiment Analysis",
|
||||
"content": original_data.get('sentiment_report', 'No sentiment report available'),
|
||||
"recent_developments": []
|
||||
},
|
||||
"fundamentals_report": {
|
||||
"title": "Fundamental Analysis",
|
||||
"content": original_data.get('fundamentals_report', 'No fundamentals report available'),
|
||||
"financial_highlights": []
|
||||
},
|
||||
"news_report": {
|
||||
"title": "Macroeconomic Context",
|
||||
"content": original_data.get('news_report', 'No news report available'),
|
||||
"key_developments": []
|
||||
},
|
||||
"investment_plan_full": {
|
||||
"title": "Complete Investment Strategy",
|
||||
"content": original_data.get('investment_plan', 'No investment plan available')
|
||||
},
|
||||
"debate_transcripts": {
|
||||
"bull_analysis": "",
|
||||
"bear_analysis": "",
|
||||
"neutral_analysis": "",
|
||||
"risk_discussion": ""
|
||||
}
|
||||
},
|
||||
"widgets_config": {
|
||||
"charts_needed": [
|
||||
{"type": "price_chart", "data_source": "financial_data.current_price", "timeframe": "30_days"},
|
||||
{"type": "technical_indicators", "data_source": "technical_indicators"}
|
||||
],
|
||||
"text_widgets": [
|
||||
{"type": "expandable_report", "title": "Technical Analysis", "content_source": "text_content.market_report"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def process_all_files(self) -> Dict[str, List[str]]:
|
||||
"""Process all JSON files in the eval_results directory"""
|
||||
results = {"success": [], "failed": []}
|
||||
|
||||
eval_results_path = Path(self.config.eval_results_path)
|
||||
|
||||
if not eval_results_path.exists():
|
||||
print(f"Eval results path does not exist: {eval_results_path}")
|
||||
return results
|
||||
|
||||
# Process each company directory
|
||||
for company_dir in eval_results_path.iterdir():
|
||||
if not company_dir.is_dir():
|
||||
continue
|
||||
|
||||
company_ticker = company_dir.name
|
||||
logs_dir = company_dir / "TradingAgentsStrategy_logs"
|
||||
transformed_dir = company_dir / "TradingAgentsStrategy_transformed_logs"
|
||||
transformed_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Process each JSON file in the logs directory
|
||||
for json_file in logs_dir.glob("*.json"):
|
||||
try:
|
||||
print(f"Processing {json_file}")
|
||||
|
||||
# Process the file
|
||||
success = self.process_single_file(str(json_file), str(transformed_dir))
|
||||
|
||||
if success:
|
||||
results["success"].append(str(json_file.name))
|
||||
print(f"Successfully transformed and saved: {json_file.name}")
|
||||
else:
|
||||
results["failed"].append(str(json_file))
|
||||
print(f"Failed to process {json_file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" {json_file}: {e}")
|
||||
results["failed"].append(str(json_file))
|
||||
|
||||
return results
|
||||
|
||||
def process_single_file(self, input_file_path: str, output_file_path: str = None) -> bool:
|
||||
"""Process a single JSON file"""
|
||||
try:
|
||||
input_path = Path(input_file_path)
|
||||
|
||||
if not input_path.exists():
|
||||
print(f"Input file does not exist: {input_path}")
|
||||
return False
|
||||
|
||||
# Load the original data
|
||||
with open(input_path, 'r') as f:
|
||||
original_data = json.load(f)
|
||||
|
||||
# Transform the data
|
||||
transformed_data = self.transform_single_file(original_data)
|
||||
|
||||
# Determine output path
|
||||
if output_file_path is None:
|
||||
output_file_path = Path(self.config.output_path) / f"{input_path.stem}_transformed.json"
|
||||
else:
|
||||
output_file_path = Path(output_file_path) / f"{input_path.stem}_transformed.json"
|
||||
|
||||
# Ensure output directory exists
|
||||
output_dir = Path(output_file_path).parent
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Save the transformed data
|
||||
with open(output_file_path, 'w') as f:
|
||||
json.dump(transformed_data, f, indent=2)
|
||||
|
||||
print(f"Successfully transformed and saved: {output_file_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to process {input_file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the transformation agent"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Transform TradingAgents output to widget-friendly format")
|
||||
parser.add_argument("--api-key", help="OpenAI API key")
|
||||
parser.add_argument("--input-file", default=" ../output_data/AVAH/TradingAgentsStrategy_logs/full_states_log_2025-07-26.json", help="Process a single input file")
|
||||
parser.add_argument("--output-file", default="../output_data/AVAH/TradingAgentsStrategy_transformed_logs/full_states_log_2025-07-26.json", help="Output file path (for single file processing)")
|
||||
parser.add_argument("--eval-results-path", default="../output_data/AVAH/TradingAgentsStrategy_logs", help="Path to eval_results directory")
|
||||
parser.add_argument("--output-path", default="../output_data/AVAH/TradingAgentsStrategy_transformed_logs/", help="Output directory path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create configuration
|
||||
config = TransformationConfig(
|
||||
openai_api_key=args.api_key,
|
||||
eval_results_path=args.eval_results_path,
|
||||
output_path=args.output_path
|
||||
)
|
||||
|
||||
# Create agent
|
||||
agent = DataTransformationAgent(config)
|
||||
|
||||
if args.input_file:
|
||||
# Process single file
|
||||
success = agent.process_single_file(args.input_file, args.output_file)
|
||||
if success:
|
||||
print("Single file processing completed successfully")
|
||||
else:
|
||||
print("Single file processing failed")
|
||||
else:
|
||||
# Process all files
|
||||
results = agent.process_all_files()
|
||||
print(f"\nProcessing completed:")
|
||||
print(f"Success: {len(results['success'])} files")
|
||||
print(f"Failed: {len(results['failed'])} files")
|
||||
|
||||
if results['success']:
|
||||
print("\nSuccessfully processed files:")
|
||||
for file_path in results['success']:
|
||||
print(f" - {file_path}")
|
||||
|
||||
if results['failed']:
|
||||
print("\nFailed to process files:")
|
||||
for file_path in results['failed']:
|
||||
print(f" - {file_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -8,7 +8,8 @@ class FinancialSituationMemory:
|
|||
if config["backend_url"] == "http://localhost:11434/v1":
|
||||
self.embedding = "nomic-embed-text"
|
||||
else:
|
||||
self.embedding = "text-embedding-3-small"
|
||||
# TODO: Replace the following with Voyage Finance-2 embedding
|
||||
self.embedding = "text-embedding-3-large"
|
||||
self.client = OpenAI(base_url=config["backend_url"])
|
||||
self.chroma_client = chromadb.Client(Settings(allow_reset=True))
|
||||
# Check if collection exists
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ ticker_to_company = {
|
|||
"UBER": "Uber",
|
||||
"ROKU": "Roku",
|
||||
"PINS": "Pinterest",
|
||||
"AVAH": "Aveanna Healthcare"
|
||||
"AVAH": "Aveanna Healthcare",
|
||||
"BTAI": "BioXcel Therapeutics"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -91,9 +92,7 @@ def fetch_top_from_category(
|
|||
parsed_line = json.loads(line)
|
||||
|
||||
# select only lines that are from the date
|
||||
post_date = datetime.utcfromtimestamp(
|
||||
parsed_line["created_utc"]
|
||||
).strftime("%Y-%m-%d")
|
||||
post_date = datetime.fromtimestamp(parsed_line["created_utc"],tz=datetime.timezone.utc).strftime("%Y-%m-%d")
|
||||
if post_date != date:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ import os
|
|||
DEFAULT_CONFIG = {
|
||||
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
|
||||
"results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"),
|
||||
"data_dir": "/home/brabus61/Desktop/Github Repos/TradingAgents/tradingagents/output_data",
|
||||
"data_dir": "/home/brabus61/Desktop/Github Repos/TradingAgents/output_data",
|
||||
"data_cache_dir": os.path.join(
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
|
||||
"dataflows/data_cache",
|
||||
),
|
||||
# LLM settings
|
||||
"llm_provider": "openai",
|
||||
"deep_think_llm": "o4-mini",
|
||||
"quick_think_llm": "gpt-4o-mini",
|
||||
"deep_think_llm": "o3",
|
||||
"quick_think_llm": "gpt-4.1",
|
||||
"backend_url": "https://api.openai.com/v1",
|
||||
# Debate and discussion settings
|
||||
"max_debate_rounds": 5,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ from .propagation import Propagator
|
|||
from .reflection import Reflector
|
||||
from .signal_processing import SignalProcessor
|
||||
|
||||
# Import data transformation agent
|
||||
from tradingagents.agents.data_visualizer.data_transformation_agent import DataTransformationAgent, TransformationConfig
|
||||
|
||||
RESULTS_BASE = os.path.join(os.path.dirname(__file__), "..", "..", "output_data")
|
||||
|
||||
class TradingAgentsGraph:
|
||||
"""Main class that orchestrates the trading agents framework."""
|
||||
|
|
@ -186,9 +190,18 @@ class TradingAgentsGraph:
|
|||
# Log state
|
||||
self._log_state(trade_date, final_state)
|
||||
|
||||
# Return decision and processed signal
|
||||
return final_state, self.process_signal(final_state["final_trade_decision"])
|
||||
eval_results_path=f"{RESULTS_BASE}"
|
||||
input_file_path = f"{eval_results_path}/{company_name}/TradingAgentsStrategy_logs/full_states_log_{trade_date}.json"
|
||||
output_file_path = f"{eval_results_path}/{company_name}/TradingAgentsStrategy_transformed_logs/"
|
||||
|
||||
# Transform output JSON into widget-friendly format
|
||||
data_transformation_agent = DataTransformationAgent(TransformationConfig(eval_results_path=eval_results_path))
|
||||
|
||||
transformed_output = data_transformation_agent.process_single_file(input_file_path, output_file_path)
|
||||
|
||||
# Return decision and processed signal
|
||||
return transformed_output, self.process_signal(final_state["final_trade_decision"])
|
||||
|
||||
def _log_state(self, trade_date, final_state):
|
||||
"""Log the final state to a JSON file."""
|
||||
self.log_states_dict[str(trade_date)] = {
|
||||
|
|
@ -222,15 +235,19 @@ class TradingAgentsGraph:
|
|||
}
|
||||
|
||||
# Save to file
|
||||
directory = Path(f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/")
|
||||
output_directory_path = os.path.join(os.path.dirname(__file__), "..", "..", "output_data",f"{self.ticker}", "TradingAgentsStrategy_logs")
|
||||
directory = Path(output_directory_path)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(
|
||||
f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/full_states_log_{trade_date}.json",
|
||||
f"{output_directory_path}/full_states_log_{trade_date}.json",
|
||||
"w",
|
||||
) as f:
|
||||
json.dump(self.log_states_dict, f, indent=4)
|
||||
|
||||
def _get_state(self, trade_date):
|
||||
return self.log_states_dict[str(trade_date)]
|
||||
|
||||
def reflect_and_remember(self, returns_losses):
|
||||
"""Reflect on decisions and update memory based on returns."""
|
||||
self.reflector.reflect_bull_researcher(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,361 @@
|
|||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any, Optional
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
import glob
|
||||
import uuid
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
# Load environment variables from .env (if present)
|
||||
try:
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
_dotenv_path = find_dotenv()
|
||||
if _dotenv_path:
|
||||
load_dotenv(_dotenv_path)
|
||||
except Exception:
|
||||
# dotenv is optional; ignore if not installed
|
||||
pass
|
||||
|
||||
# Import your TradingAgents components
|
||||
import sys
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
app = FastAPI(title="TradingAgents API", version="1.0.0", debug=True)
|
||||
|
||||
# Centralized results directory to avoid repetition
|
||||
RESULTS_BASE = os.path.join(os.path.dirname(__file__), "..", "..", "output_data")
|
||||
|
||||
# Simple startup check for OPENAI_API_KEY
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
if not OPENAI_API_KEY:
|
||||
print("[WARN] OPENAI_API_KEY is not set. Set it in your shell or in a .env file.")
|
||||
else:
|
||||
print("[INFO] OPENAI_API_KEY detected from environment.")
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:3000"], # React dev server
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Pydantic models
|
||||
class AnalysisRequest(BaseModel):
|
||||
symbol: str
|
||||
date: str
|
||||
config_overrides: Optional[Dict[str, Any]] = None
|
||||
|
||||
class AnalysisResponse(BaseModel):
|
||||
job_id: str
|
||||
status: str
|
||||
message: str
|
||||
|
||||
class JobStatus(BaseModel):
|
||||
job_id: str
|
||||
status: str
|
||||
progress: Optional[str] = None
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
# In-memory job storage (in production, use Redis or database)
|
||||
jobs: Dict[str, JobStatus] = {}
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "TradingAgents API is running"}
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
||||
|
||||
async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config_overrides: Dict[str, Any] = None):
|
||||
"""Background task to run the trading analysis without blocking the event loop"""
|
||||
try:
|
||||
jobs[job_id].status = "running"
|
||||
jobs[job_id].progress = "Initializing TradingAgents..."
|
||||
|
||||
# Prepare config
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
if config_overrides:
|
||||
config.update(config_overrides)
|
||||
|
||||
jobs[job_id].progress = "Setting up trading graph..."
|
||||
|
||||
# Define blocking work as sync function
|
||||
def _do_work():
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
jobs[job_id].progress = f"Analyzing {symbol} for {analysis_date}..."
|
||||
_, decision = ta.propagate(symbol, analysis_date)
|
||||
return decision
|
||||
|
||||
# Run blocking work in threadpool so the event loop stays responsive
|
||||
decision = await run_in_threadpool(_do_work)
|
||||
|
||||
jobs[job_id].status = "completed"
|
||||
jobs[job_id].result = {
|
||||
"symbol": symbol,
|
||||
"date": analysis_date,
|
||||
"decision": decision,
|
||||
"completed_at": datetime.now().isoformat(),
|
||||
}
|
||||
jobs[job_id].progress = "Analysis completed successfully"
|
||||
|
||||
except Exception as e:
|
||||
jobs[job_id].status = "failed"
|
||||
jobs[job_id].error = str(e)
|
||||
jobs[job_id].progress = f"Error: {str(e)}"
|
||||
|
||||
@app.post("/analysis/start", response_model=AnalysisResponse)
|
||||
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(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,
|
||||
symbol,
|
||||
date,
|
||||
request.config_overrides or {}
|
||||
)
|
||||
|
||||
return AnalysisResponse(
|
||||
job_id=job_id,
|
||||
status="queued",
|
||||
message=f"Analysis started for {symbol} on {date}"
|
||||
)
|
||||
|
||||
@app.get("/analysis/status/{job_id}", response_model=JobStatus)
|
||||
async def get_analysis_status(job_id: str):
|
||||
"""Get the status of a running analysis"""
|
||||
if job_id not in jobs:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
|
||||
return jobs[job_id]
|
||||
|
||||
@app.get("/results/companies")
|
||||
async def get_companies():
|
||||
"""Get list of companies with analysis results"""
|
||||
results_dir = RESULTS_BASE
|
||||
if not os.path.exists(results_dir):
|
||||
return {"companies": []}
|
||||
|
||||
companies = []
|
||||
for company_dir in os.listdir(results_dir):
|
||||
company_path = os.path.join(results_dir, company_dir)
|
||||
if os.path.isdir(company_path):
|
||||
# Check both regular logs and transformed logs
|
||||
logs_dir = os.path.join(company_path, "TradingAgentsStrategy_logs")
|
||||
transformed_logs_dir = os.path.join(company_path, "TradingAgentsStrategy_transformed_logs")
|
||||
|
||||
total_analyses = 0
|
||||
latest_date = None
|
||||
|
||||
# Count regular analyses
|
||||
if os.path.exists(logs_dir):
|
||||
json_files = glob.glob(os.path.join(logs_dir, "*.json"))
|
||||
total_analyses += len(json_files)
|
||||
if json_files:
|
||||
latest_file = max(json_files, key=os.path.getctime)
|
||||
latest_date = os.path.basename(latest_file).replace("full_states_log_", "").replace(".json", "")
|
||||
|
||||
# Count transformed analyses
|
||||
transformed_count = 0
|
||||
if os.path.exists(transformed_logs_dir):
|
||||
transformed_files = glob.glob(os.path.join(transformed_logs_dir, "*_transformed.json"))
|
||||
transformed_count = len(transformed_files)
|
||||
|
||||
if total_analyses > 0 or transformed_count > 0:
|
||||
companies.append({
|
||||
"symbol": company_dir,
|
||||
"latest_analysis": latest_date,
|
||||
"total_analyses": total_analyses,
|
||||
"transformed_analyses": transformed_count
|
||||
})
|
||||
|
||||
return {"companies": companies}
|
||||
|
||||
@app.get("/results/{symbol}")
|
||||
async def get_company_results(symbol: str):
|
||||
"""Get all analysis results for a specific company"""
|
||||
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}")
|
||||
|
||||
results = []
|
||||
json_files = glob.glob(os.path.join(results_dir, "*.json"))
|
||||
|
||||
for file_path in sorted(json_files, key=os.path.getctime, reverse=True):
|
||||
filename = os.path.basename(file_path)
|
||||
analysis_date = filename.replace("full_states_log_", "").replace(".json", "")
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
results.append({
|
||||
"date": analysis_date,
|
||||
"filename": filename,
|
||||
"file_size": os.path.getsize(file_path),
|
||||
"modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat(),
|
||||
"preview": {
|
||||
"keys": list(data.keys()) if isinstance(data, dict) else "Not a dict",
|
||||
"size": len(str(data))
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"date": analysis_date,
|
||||
"filename": filename,
|
||||
"error": f"Could not read file: {str(e)}"
|
||||
})
|
||||
|
||||
return {"symbol": symbol.upper(), "results": results}
|
||||
|
||||
@app.get("/transformed-results/{symbol}")
|
||||
async def get_transformed_company_results(symbol: str):
|
||||
"""Get all transformed analysis results for a specific company"""
|
||||
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}")
|
||||
|
||||
results = []
|
||||
json_files = glob.glob(os.path.join(results_dir, "*_transformed.json"))
|
||||
|
||||
for file_path in sorted(json_files, key=os.path.getctime, reverse=True):
|
||||
filename = os.path.basename(file_path)
|
||||
# Extract date from filename like "full_states_log_2025-07-26_transformed.json"
|
||||
analysis_date = filename.replace("full_states_log_", "").replace("_transformed.json", "")
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
results.append({
|
||||
"date": analysis_date,
|
||||
"filename": filename,
|
||||
"file_size": os.path.getsize(file_path),
|
||||
"modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat(),
|
||||
"preview": {
|
||||
"company_ticker": data.get("metadata", {}).get("company_ticker", "N/A"),
|
||||
"final_recommendation": data.get("metadata", {}).get("final_recommendation", "N/A"),
|
||||
"confidence_level": data.get("metadata", {}).get("confidence_level", "N/A"),
|
||||
"current_price": data.get("financial_data", {}).get("current_price", 0)
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"date": analysis_date,
|
||||
"filename": filename,
|
||||
"error": f"Could not read file: {str(e)}"
|
||||
})
|
||||
|
||||
return {"symbol": symbol.upper(), "results": results}
|
||||
|
||||
@app.get("/results/{symbol}/{date}")
|
||||
async def get_specific_result(symbol: str, date: str):
|
||||
"""Get specific analysis result"""
|
||||
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}")
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
return {
|
||||
"symbol": symbol.upper(),
|
||||
"date": date,
|
||||
"data": data,
|
||||
"metadata": {
|
||||
"file_size": os.path.getsize(file_path),
|
||||
"modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error reading result: {str(e)}")
|
||||
|
||||
@app.get("/transformed-results/{symbol}/{date}")
|
||||
async def get_specific_transformed_result(symbol: str, date: str):
|
||||
"""Get specific transformed analysis result"""
|
||||
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}")
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
return {
|
||||
"symbol": symbol.upper(),
|
||||
"date": date,
|
||||
"data": data,
|
||||
"file_info": {
|
||||
"filename": os.path.basename(file_path),
|
||||
"file_size": os.path.getsize(file_path),
|
||||
"modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}")
|
||||
|
||||
@app.get("/jobs")
|
||||
async def get_jobs():
|
||||
"""Get all jobs"""
|
||||
job_lst = []
|
||||
for job_id, job in jobs.items():
|
||||
job_lst.append({
|
||||
"job_id": job_id,
|
||||
"status": job.status,
|
||||
"progress": job.progress,
|
||||
"result": job.result,
|
||||
"error": job.error
|
||||
})
|
||||
return {"jobs": job_lst}
|
||||
|
||||
@app.get("/config")
|
||||
async def get_default_config():
|
||||
"""Get the default configuration"""
|
||||
return {"config": DEFAULT_CONFIG}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "tradingagents-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@types/node": "^18.15.0",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"axios": "^1.5.1",
|
||||
"chart.js": "^4.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"lucide-react": "^0.288.0",
|
||||
"postcss": "^8.4.31",
|
||||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^2.8.0",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^3.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"proxy": "http://localhost:8000"
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1e40af;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#2563eb;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#4f46e5;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="chartGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#60a5fa;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:0.3" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background with rounded corners -->
|
||||
<rect width="32" height="32" rx="7" fill="url(#bgGradient)"/>
|
||||
|
||||
<!-- Timeseries chart line -->
|
||||
<path d="M4 24 L8 20 L12 22 L16 18 L20 16 L24 14 L28 12"
|
||||
stroke="#ffffff"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
opacity="0.9"/>
|
||||
|
||||
<!-- Chart area fill -->
|
||||
<path d="M4 24 L8 20 L12 22 L16 18 L20 16 L24 14 L28 12 L28 28 L4 28 Z"
|
||||
fill="url(#chartGradient)"/>
|
||||
|
||||
<!-- AI Agent nodes (neural network style) -->
|
||||
<circle cx="6" cy="6" r="2" fill="#ffffff" opacity="0.9"/>
|
||||
<circle cx="16" cy="4" r="2" fill="#ffffff" opacity="0.9"/>
|
||||
<circle cx="26" cy="6" r="2" fill="#ffffff" opacity="0.9"/>
|
||||
<circle cx="11" cy="10" r="1.5" fill="#ffffff" opacity="0.7"/>
|
||||
<circle cx="21" cy="10" r="1.5" fill="#ffffff" opacity="0.7"/>
|
||||
|
||||
<!-- AI Agent connections -->
|
||||
<line x1="6" y1="6" x2="11" y2="10" stroke="#ffffff" stroke-width="1" opacity="0.6"/>
|
||||
<line x1="16" y1="4" x2="11" y2="10" stroke="#ffffff" stroke-width="1" opacity="0.6"/>
|
||||
<line x1="16" y1="4" x2="21" y2="10" stroke="#ffffff" stroke-width="1" opacity="0.6"/>
|
||||
<line x1="26" y1="6" x2="21" y2="10" stroke="#ffffff" stroke-width="1" opacity="0.6"/>
|
||||
<line x1="6" y1="6" x2="16" y2="4" stroke="#ffffff" stroke-width="1" opacity="0.4"/>
|
||||
<line x1="16" y1="4" x2="26" y2="6" stroke="#ffffff" stroke-width="1" opacity="0.4"/>
|
||||
|
||||
<!-- Data points on the chart -->
|
||||
<circle cx="8" cy="20" r="1" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="12" cy="22" r="1" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="16" cy="18" r="1" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="20" cy="16" r="1" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="24" cy="14" r="1" fill="#ffffff" opacity="0.8"/>
|
||||
|
||||
<!-- AI brain symbol in top-right -->
|
||||
<path d="M24 8 Q26 6 28 8 Q28 10 26 10 Q24 10 24 8"
|
||||
fill="#ffffff"
|
||||
opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="TradingAgents - Advanced AI-powered trading analysis platform"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>TradingAgents - AI Trading Analysis</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"short_name": "TradingAgents",
|
||||
"name": "TradingAgents - AI-Powered Trading Analysis",
|
||||
"description": "Advanced AI-driven multi-agent systems for trading analysis and market insights",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#2563eb",
|
||||
"background_color": "#ffffff",
|
||||
"orientation": "portrait-primary",
|
||||
"categories": ["finance", "business", "productivity"],
|
||||
"lang": "en-US"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">TradingAgents Web Application</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Backend is running successfully! Frontend compilation test.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-medium text-gray-900 mb-4">System Status</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full mr-3"></div>
|
||||
<span>Backend Server: Running on http://localhost:8000</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full mr-3"></div>
|
||||
<span>Frontend: Compilation successful</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import React from 'react';
|
||||
import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
|
||||
|
||||
interface TradingAgentsResult {
|
||||
symbol: string;
|
||||
final_decision?: {
|
||||
decision: string;
|
||||
reasoning: string;
|
||||
};
|
||||
technical_analysis?: {
|
||||
current_price: number;
|
||||
rsi: number;
|
||||
macd: number;
|
||||
moving_averages: {
|
||||
ma_50: number;
|
||||
ma_200: number;
|
||||
};
|
||||
};
|
||||
fundamental_analysis?: {
|
||||
market_cap: string;
|
||||
ps_ratio: number;
|
||||
forward_pe: number;
|
||||
analyst_target: number;
|
||||
};
|
||||
bull_arguments?: string[];
|
||||
bear_arguments?: string[];
|
||||
neutral_perspective?: string;
|
||||
risk_assessment?: {
|
||||
overall_risk: number;
|
||||
};
|
||||
sentiment_analysis?: {
|
||||
overall_score: number;
|
||||
};
|
||||
ownership_structure?: {
|
||||
insider_ownership: number;
|
||||
institutional_ownership: number;
|
||||
retail_ownership: number;
|
||||
};
|
||||
investment_plan?: {
|
||||
stop_loss: number;
|
||||
profit_targets: number[];
|
||||
};
|
||||
earnings_date?: string;
|
||||
}
|
||||
|
||||
interface AnalysisDataAdapterProps {
|
||||
tradingResult: TradingAgentsResult;
|
||||
}
|
||||
|
||||
const AnalysisDataAdapter: React.FC<AnalysisDataAdapterProps> = ({ tradingResult }) => {
|
||||
// Transform TradingAgents result into AnalysisWidgets format
|
||||
const transformedData = {
|
||||
symbol: tradingResult.symbol || 'N/A',
|
||||
currentPrice: tradingResult.technical_analysis?.current_price || 5.81,
|
||||
marketCap: tradingResult.fundamental_analysis?.market_cap || '$814.80M',
|
||||
psRatio: tradingResult.fundamental_analysis?.ps_ratio || 0.4,
|
||||
forwardPE: tradingResult.fundamental_analysis?.forward_pe || 23.62,
|
||||
targetPrice: tradingResult.fundamental_analysis?.analyst_target || 5.25,
|
||||
rsi: tradingResult.technical_analysis?.rsi || 38.39,
|
||||
macd: tradingResult.technical_analysis?.macd || -0.208,
|
||||
ma50: tradingResult.technical_analysis?.moving_averages?.ma_50 || 4.61,
|
||||
ma200: tradingResult.technical_analysis?.moving_averages?.ma_200 || 4.88,
|
||||
stopLoss: tradingResult.investment_plan?.stop_loss || 3.00,
|
||||
profitTarget1: tradingResult.investment_plan?.profit_targets?.[0] || 5.00,
|
||||
profitTarget2: tradingResult.investment_plan?.profit_targets?.[1] || 7.50,
|
||||
riskLevel: tradingResult.risk_assessment?.overall_risk || 45,
|
||||
bullArguments: tradingResult.bull_arguments || [
|
||||
'Strong revenue growth potential in emerging markets',
|
||||
'Innovative product pipeline with competitive advantages',
|
||||
'Undervalued compared to industry peers',
|
||||
'Improving operational efficiency and margin expansion',
|
||||
'Strategic partnerships driving market penetration'
|
||||
],
|
||||
bearArguments: tradingResult.bear_arguments || [
|
||||
'Intense competition from established players',
|
||||
'Regulatory headwinds in key markets',
|
||||
'High customer acquisition costs',
|
||||
'Dependence on volatile market conditions',
|
||||
'Execution risks in scaling operations'
|
||||
],
|
||||
neutralPerspective: tradingResult.neutral_perspective ||
|
||||
'The investment presents a balanced risk-reward profile with both compelling growth opportunities and legitimate concerns. Key factors to monitor include execution on strategic initiatives, competitive positioning, and market dynamics.',
|
||||
earningsDate: tradingResult.earnings_date || 'August 7, 2024',
|
||||
sentimentScore: tradingResult.sentiment_analysis?.overall_score || 65,
|
||||
insiderOwnership: tradingResult.ownership_structure?.insider_ownership || 2.74,
|
||||
institutionalOwnership: tradingResult.ownership_structure?.institutional_ownership || 20.26,
|
||||
retailOwnership: tradingResult.ownership_structure?.retail_ownership || 77.00,
|
||||
finalDecision: tradingResult.final_decision?.decision || 'BUY',
|
||||
decisionReasoning: tradingResult.final_decision?.reasoning ||
|
||||
'Based on comprehensive analysis, the stock shows strong fundamentals with reasonable valuation and positive technical momentum, warranting a buy recommendation with appropriate risk management.'
|
||||
};
|
||||
|
||||
return <AnalysisWidgets data={transformedData} rawData={tradingResult} />;
|
||||
};
|
||||
|
||||
export default AnalysisDataAdapter;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { BarChart3, Play, Database, Home } from 'lucide-react';
|
||||
|
||||
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const location = useLocation();
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/', icon: Home },
|
||||
{ name: 'Run Analysis', href: '/run-analysis', icon: Play },
|
||||
{ name: 'View Agent Outputs', href: '/results', icon: Database },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<nav className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<BarChart3 className="h-8 w-8 text-primary-600" />
|
||||
<span className="ml-2 text-xl font-bold text-gray-900">TradingAgents</span>
|
||||
</div>
|
||||
<div className="flex space-x-8">
|
||||
{navigation.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = location.pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={`inline-flex items-center px-1 pt-1 text-sm font-medium ${
|
||||
isActive
|
||||
? 'border-b-2 border-primary-500 text-gray-900'
|
||||
: 'text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4 mr-2" />
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface NewsItem {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
timestamp: string;
|
||||
category: 'macro' | 'company' | 'sector';
|
||||
impact: 'positive' | 'negative' | 'neutral';
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface NewsFeedWidgetProps {
|
||||
symbol: string;
|
||||
maxItems?: number;
|
||||
onBack?: () => void;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
const NewsFeedWidget: React.FC<NewsFeedWidgetProps> = ({ symbol, maxItems = 10, onBack, onRefresh }) => {
|
||||
const [newsItems, setNewsItems] = useState<NewsItem[]>([]);
|
||||
const [filter, setFilter] = useState<'all' | 'macro' | 'company' | 'sector'>('all');
|
||||
|
||||
const handleBack = () => {
|
||||
if (onBack) return onBack();
|
||||
if (typeof window !== 'undefined' && window.history) window.history.back();
|
||||
};
|
||||
const handleRefresh = () => {
|
||||
if (onRefresh) return onRefresh();
|
||||
if (typeof window !== 'undefined') window.location.reload();
|
||||
};
|
||||
|
||||
// Mock news data - in production, this would come from your backend
|
||||
useEffect(() => {
|
||||
const mockNews: NewsItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Federal Reserve Signals Potential Rate Cuts',
|
||||
summary: 'Fed Chairman indicates possible monetary policy easing in response to economic indicators.',
|
||||
timestamp: '2 hours ago',
|
||||
category: 'macro',
|
||||
impact: 'positive',
|
||||
source: 'Reuters'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: `${symbol} Announces Strategic Partnership`,
|
||||
summary: 'Company enters into major partnership agreement to expand market reach.',
|
||||
timestamp: '4 hours ago',
|
||||
category: 'company',
|
||||
impact: 'positive',
|
||||
source: 'Business Wire'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Trade Policy Updates Impact Tech Sector',
|
||||
summary: 'New trade regulations expected to affect technology companies operations.',
|
||||
timestamp: '6 hours ago',
|
||||
category: 'sector',
|
||||
impact: 'negative',
|
||||
source: 'Financial Times'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Market Volatility Increases Amid Economic Uncertainty',
|
||||
summary: 'Global markets show increased volatility as investors assess economic conditions.',
|
||||
timestamp: '8 hours ago',
|
||||
category: 'macro',
|
||||
impact: 'negative',
|
||||
source: 'Bloomberg'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: `${symbol} Q2 Earnings Preview`,
|
||||
summary: 'Analysts expect strong quarterly results driven by revenue growth initiatives.',
|
||||
timestamp: '12 hours ago',
|
||||
category: 'company',
|
||||
impact: 'positive',
|
||||
source: 'MarketWatch'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Sector Rotation Favors Growth Stocks',
|
||||
summary: 'Institutional investors showing renewed interest in growth-oriented companies.',
|
||||
timestamp: '1 day ago',
|
||||
category: 'sector',
|
||||
impact: 'positive',
|
||||
source: 'Wall Street Journal'
|
||||
}
|
||||
];
|
||||
setNewsItems(mockNews);
|
||||
}, [symbol]);
|
||||
|
||||
const filteredNews = newsItems.filter(item =>
|
||||
filter === 'all' || item.category === filter
|
||||
).slice(0, maxItems);
|
||||
|
||||
const getImpactColor = (impact: string) => {
|
||||
switch (impact) {
|
||||
case 'positive': return 'text-green-600 bg-green-50 border-green-200';
|
||||
case 'negative': return 'text-red-600 bg-red-50 border-red-200';
|
||||
default: return 'text-gray-600 bg-gray-50 border-gray-200';
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category) {
|
||||
case 'macro': return '🌍';
|
||||
case 'company': return '🏢';
|
||||
case 'sector': return '📊';
|
||||
default: return '📰';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Back"
|
||||
title="Back"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<h3 className="text-lg font-semibold">News Feed</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRefresh}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
⟳
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex-1" />
|
||||
<div className="flex space-x-2">
|
||||
{['all', 'macro', 'company', 'sector'].map((filterOption) => (
|
||||
<button
|
||||
key={filterOption}
|
||||
onClick={() => setFilter(filterOption as any)}
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium capitalize ${
|
||||
filter === filterOption
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{filterOption}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{filteredNews.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`p-3 rounded-lg border ${getImpactColor(item.impact)}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-lg">{getCategoryIcon(item.category)}</span>
|
||||
<span className="text-xs font-medium text-gray-500 uppercase">
|
||||
{item.category}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400">{item.timestamp}</span>
|
||||
</div>
|
||||
|
||||
<h4 className="font-medium text-sm mb-1 text-gray-900">
|
||||
{item.title}
|
||||
</h4>
|
||||
|
||||
<p className="text-xs text-gray-600 mb-2">
|
||||
{item.summary}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-gray-500">{item.source}</span>
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
item.impact === 'positive' ? 'bg-green-100 text-green-700' :
|
||||
item.impact === 'negative' ? 'bg-red-100 text-red-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{item.impact}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredNews.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p>No news items found for the selected filter.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsFeedWidget;
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
import React from 'react';
|
||||
|
||||
// New interface for the transformed JSON structure
|
||||
interface TransformedAnalysisData {
|
||||
metadata: {
|
||||
company_ticker: string;
|
||||
company_name: string;
|
||||
analysis_date: string;
|
||||
final_recommendation: 'BUY' | 'SELL' | 'HOLD';
|
||||
confidence_level: 'HIGH' | 'MEDIUM' | 'LOW';
|
||||
};
|
||||
|
||||
financial_data: {
|
||||
current_price: number;
|
||||
price_change: number;
|
||||
price_change_percent: number;
|
||||
market_cap: string;
|
||||
enterprise_value: string;
|
||||
shares_outstanding: string;
|
||||
trading_range: {
|
||||
high: number;
|
||||
low: number;
|
||||
open: number;
|
||||
};
|
||||
volume: number;
|
||||
valuation_ratios: {
|
||||
current_ps_ratio: number;
|
||||
fair_value_ps_ratio: number;
|
||||
forward_pe: number;
|
||||
forward_ps: number;
|
||||
forward_pcf: number;
|
||||
forward_pocf: number;
|
||||
};
|
||||
ownership: {
|
||||
insider_percent: number;
|
||||
institutional_percent: number;
|
||||
};
|
||||
analyst_data: {
|
||||
consensus_rating: string;
|
||||
price_target: number;
|
||||
forecast_price: number;
|
||||
};
|
||||
};
|
||||
|
||||
technical_indicators: {
|
||||
sma_50: number;
|
||||
sma_200: number;
|
||||
ema_10: number;
|
||||
macd: number;
|
||||
macd_signal: number;
|
||||
rsi: number;
|
||||
atr: number;
|
||||
trend_directions: {
|
||||
sma_50: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
|
||||
sma_200: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
|
||||
ema_10: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
|
||||
macd: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
|
||||
rsi_condition: 'OVERSOLD' | 'OVERBOUGHT' | 'NEUTRAL';
|
||||
};
|
||||
};
|
||||
|
||||
investment_strategy: {
|
||||
position_sizing: {
|
||||
total_allocation_percent: string;
|
||||
entry_strategy: string;
|
||||
tranche_1_percent: string;
|
||||
tranche_2_percent: string;
|
||||
};
|
||||
risk_management: {
|
||||
initial_stop_loss: number;
|
||||
stop_loss_percent: number;
|
||||
breakeven_strategy: string;
|
||||
};
|
||||
profit_targets: Array<{
|
||||
target_price: number;
|
||||
action: string;
|
||||
rationale: string;
|
||||
}>;
|
||||
monitoring_points: string[];
|
||||
};
|
||||
|
||||
debate_summary: {
|
||||
bull_key_points: string[];
|
||||
bear_key_points: string[];
|
||||
neutral_perspective: string;
|
||||
final_decision_rationale: string;
|
||||
};
|
||||
|
||||
text_content: {
|
||||
market_report: {
|
||||
title: string;
|
||||
content: string;
|
||||
key_takeaways: string[];
|
||||
};
|
||||
sentiment_report: {
|
||||
title: string;
|
||||
content: string;
|
||||
recent_developments: string[];
|
||||
};
|
||||
fundamentals_report: {
|
||||
title: string;
|
||||
content: string;
|
||||
financial_highlights: string[];
|
||||
};
|
||||
news_report: {
|
||||
title: string;
|
||||
content: string;
|
||||
key_developments: Array<{
|
||||
date: string;
|
||||
event: string;
|
||||
impact: string;
|
||||
}>;
|
||||
};
|
||||
investment_plan_full: {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
debate_transcripts: {
|
||||
bull_analysis: string;
|
||||
bear_analysis: string;
|
||||
neutral_analysis: string;
|
||||
risk_discussion: string;
|
||||
};
|
||||
};
|
||||
|
||||
widgets_config: {
|
||||
charts_needed: Array<{
|
||||
type: string;
|
||||
data_source: string;
|
||||
timeframe: string;
|
||||
}>;
|
||||
text_widgets: Array<{
|
||||
type: string;
|
||||
title: string;
|
||||
content_source: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy interface for backward compatibility
|
||||
interface LegacyTradingAgentsResult {
|
||||
symbol: string;
|
||||
final_decision?: {
|
||||
decision: string;
|
||||
reasoning: string;
|
||||
};
|
||||
technical_analysis?: {
|
||||
current_price: number;
|
||||
rsi: number;
|
||||
macd: number;
|
||||
moving_averages: {
|
||||
ma_50: number;
|
||||
ma_200: number;
|
||||
};
|
||||
};
|
||||
fundamental_analysis?: {
|
||||
market_cap: string;
|
||||
ps_ratio: number;
|
||||
forward_pe: number;
|
||||
analyst_target: number;
|
||||
};
|
||||
bull_arguments?: string[];
|
||||
bear_arguments?: string[];
|
||||
neutral_perspective?: string;
|
||||
risk_assessment?: {
|
||||
overall_risk: number;
|
||||
};
|
||||
sentiment_analysis?: {
|
||||
overall_score: number;
|
||||
};
|
||||
ownership_structure?: {
|
||||
insider_ownership: number;
|
||||
institutional_ownership: number;
|
||||
retail_ownership: number;
|
||||
};
|
||||
investment_plan?: {
|
||||
stop_loss: number;
|
||||
profit_targets: number[];
|
||||
};
|
||||
earnings_date?: string;
|
||||
}
|
||||
|
||||
interface TransformedDataAdapterProps {
|
||||
analysisData: TransformedAnalysisData | LegacyTradingAgentsResult;
|
||||
}
|
||||
|
||||
const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysisData }) => {
|
||||
// Check if this is the new transformed format or legacy format
|
||||
const isTransformedFormat = (data: any): data is TransformedAnalysisData => {
|
||||
return data.metadata && data.financial_data && data.technical_indicators;
|
||||
};
|
||||
|
||||
// Convert legacy format to new format for backward compatibility
|
||||
const convertLegacyToTransformed = (legacyData: LegacyTradingAgentsResult): TransformedAnalysisData => {
|
||||
return {
|
||||
metadata: {
|
||||
company_ticker: legacyData.symbol || 'UNKNOWN',
|
||||
company_name: legacyData.symbol || 'Unknown Company',
|
||||
analysis_date: new Date().toISOString().split('T')[0],
|
||||
final_recommendation: (legacyData.final_decision?.decision?.toUpperCase() as 'BUY' | 'SELL' | 'HOLD') || 'HOLD',
|
||||
confidence_level: 'MEDIUM'
|
||||
},
|
||||
financial_data: {
|
||||
current_price: legacyData.technical_analysis?.current_price || 0,
|
||||
price_change: 0,
|
||||
price_change_percent: 0,
|
||||
market_cap: legacyData.fundamental_analysis?.market_cap || 'N/A',
|
||||
enterprise_value: 'N/A',
|
||||
shares_outstanding: 'N/A',
|
||||
trading_range: {
|
||||
high: 0,
|
||||
low: 0,
|
||||
open: 0
|
||||
},
|
||||
volume: 0,
|
||||
valuation_ratios: {
|
||||
current_ps_ratio: legacyData.fundamental_analysis?.ps_ratio || 0,
|
||||
fair_value_ps_ratio: 0,
|
||||
forward_pe: legacyData.fundamental_analysis?.forward_pe || 0,
|
||||
forward_ps: 0,
|
||||
forward_pcf: 0,
|
||||
forward_pocf: 0
|
||||
},
|
||||
ownership: {
|
||||
insider_percent: legacyData.ownership_structure?.insider_ownership || 0,
|
||||
institutional_percent: legacyData.ownership_structure?.institutional_ownership || 0
|
||||
},
|
||||
analyst_data: {
|
||||
consensus_rating: 'N/A',
|
||||
price_target: legacyData.fundamental_analysis?.analyst_target || 0,
|
||||
forecast_price: 0
|
||||
}
|
||||
},
|
||||
technical_indicators: {
|
||||
sma_50: legacyData.technical_analysis?.moving_averages?.ma_50 || 0,
|
||||
sma_200: legacyData.technical_analysis?.moving_averages?.ma_200 || 0,
|
||||
ema_10: 0,
|
||||
macd: legacyData.technical_analysis?.macd || 0,
|
||||
macd_signal: 0,
|
||||
rsi: legacyData.technical_analysis?.rsi || 50,
|
||||
atr: 0,
|
||||
trend_directions: {
|
||||
sma_50: 'NEUTRAL',
|
||||
sma_200: 'NEUTRAL',
|
||||
ema_10: 'NEUTRAL',
|
||||
macd: 'NEUTRAL',
|
||||
rsi_condition: 'NEUTRAL'
|
||||
}
|
||||
},
|
||||
investment_strategy: {
|
||||
position_sizing: {
|
||||
total_allocation_percent: '0%',
|
||||
entry_strategy: 'N/A',
|
||||
tranche_1_percent: '0%',
|
||||
tranche_2_percent: '0%'
|
||||
},
|
||||
risk_management: {
|
||||
initial_stop_loss: legacyData.investment_plan?.stop_loss || 0,
|
||||
stop_loss_percent: 0,
|
||||
breakeven_strategy: 'N/A'
|
||||
},
|
||||
profit_targets: legacyData.investment_plan?.profit_targets?.map(target => ({
|
||||
target_price: target,
|
||||
action: 'SELL',
|
||||
rationale: 'Profit target'
|
||||
})) || [],
|
||||
monitoring_points: []
|
||||
},
|
||||
debate_summary: {
|
||||
bull_key_points: legacyData.bull_arguments || [],
|
||||
bear_key_points: legacyData.bear_arguments || [],
|
||||
neutral_perspective: legacyData.neutral_perspective || 'No neutral perspective available',
|
||||
final_decision_rationale: legacyData.final_decision?.reasoning || 'No decision rationale available'
|
||||
},
|
||||
text_content: {
|
||||
market_report: {
|
||||
title: 'Technical Analysis Report',
|
||||
content: 'Legacy data - detailed technical analysis not available',
|
||||
key_takeaways: []
|
||||
},
|
||||
sentiment_report: {
|
||||
title: 'Company Sentiment Analysis',
|
||||
content: 'Legacy data - detailed sentiment analysis not available',
|
||||
recent_developments: []
|
||||
},
|
||||
fundamentals_report: {
|
||||
title: 'Fundamental Analysis',
|
||||
content: 'Legacy data - detailed fundamental analysis not available',
|
||||
financial_highlights: []
|
||||
},
|
||||
news_report: {
|
||||
title: 'Macroeconomic Context',
|
||||
content: 'Legacy data - news report not available',
|
||||
key_developments: []
|
||||
},
|
||||
investment_plan_full: {
|
||||
title: 'Complete Investment Strategy',
|
||||
content: 'Legacy data - detailed investment plan not available'
|
||||
},
|
||||
debate_transcripts: {
|
||||
bull_analysis: legacyData.bull_arguments?.join('\n') || '',
|
||||
bear_analysis: legacyData.bear_arguments?.join('\n') || '',
|
||||
neutral_analysis: legacyData.neutral_perspective || '',
|
||||
risk_discussion: ''
|
||||
}
|
||||
},
|
||||
widgets_config: {
|
||||
charts_needed: [
|
||||
{ type: 'price_chart', data_source: 'financial_data.current_price', timeframe: '30_days' },
|
||||
{ type: 'technical_indicators', data_source: 'technical_indicators', timeframe: '30_days' }
|
||||
],
|
||||
text_widgets: [
|
||||
{ type: 'expandable_report', title: 'Technical Analysis', content_source: 'text_content.market_report' }
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Get the transformed data (either already transformed or converted from legacy)
|
||||
const transformedData: TransformedAnalysisData = isTransformedFormat(analysisData)
|
||||
? analysisData
|
||||
: convertLegacyToTransformed(analysisData);
|
||||
|
||||
// Builds a default widgets_config for transformed analyses when missing
|
||||
const buildTransformedWidgetsConfig = (data: any) => {
|
||||
const confidenceMap: Record<string, number> = { LOW: 33, MEDIUM: 66, HIGH: 90 };
|
||||
return {
|
||||
layout: [
|
||||
["kpi_recommendation", "kpi_confidence", "kpi_price_change", "kpi_marketcap", "kpi_ev"],
|
||||
["ownership_donut", "range_bar", "volume_chip"],
|
||||
["rsi_gauge", "macd_mini", "ma_trends", "atr_chip"],
|
||||
["strategy_ladder", "position_sizing", "monitoring_points"],
|
||||
["analyst_target_vs_price"],
|
||||
["report_technical", "report_sentiment"],
|
||||
["report_fundamentals", "report_investment_plan"],
|
||||
["debate_summary"]
|
||||
],
|
||||
widgets: [
|
||||
{ id: "kpi_recommendation", type: "kpi_badge", title: "Recommendation", dataPath: "metadata.final_recommendation", colorMapping: { BUY: "green", HOLD: "yellow", SELL: "red" } },
|
||||
{ id: "kpi_confidence", type: "radial_gauge", title: "Confidence", value: confidenceMap[(data?.metadata?.confidence_level || "MEDIUM").toUpperCase()] || 66 },
|
||||
{ id: "kpi_price_change", type: "price_kpi", title: "Current Price", valuePath: "financial_data.current_price", deltaPath: "financial_data.price_change_percent", format: { value: "currency", delta: "percent" } },
|
||||
{ id: "kpi_marketcap", type: "kpi_text", title: "Market Cap", dataPath: "financial_data.market_cap" },
|
||||
{ id: "kpi_ev", type: "kpi_text", title: "Enterprise Value", dataPath: "financial_data.enterprise_value" },
|
||||
{ id: "ownership_donut", type: "donut", title: "Ownership", series: [
|
||||
{ label: "Insider %", valuePath: "financial_data.ownership.insider_percent" },
|
||||
{ label: "Institutional %", valuePath: "financial_data.ownership.institutional_percent" }
|
||||
], valueFormat: "percent" },
|
||||
{ id: "range_bar", type: "range_bar", title: "Trading Range", lowPath: "financial_data.trading_range.low", highPath: "financial_data.trading_range.high", openPath: "financial_data.trading_range.open", currentPath: "financial_data.current_price" },
|
||||
{ id: "volume_chip", type: "kpi_text", title: "Volume", dataPath: "financial_data.volume", format: { value: "number_compact" } },
|
||||
{ id: "rsi_gauge", type: "linear_gauge", title: "RSI", dataPath: "technical_indicators.rsi", min: 0, max: 100, zones: [ { to: 30, color: "blue", label: "Oversold" }, { from: 70, to: 100, color: "red", label: "Overbought" } ] },
|
||||
{ id: "macd_mini", type: "macd_snapshot", title: "MACD", macdPath: "technical_indicators.macd", signalPath: "technical_indicators.macd_signal", histogramCompute: "macd - macd_signal" },
|
||||
{ id: "ma_trends", type: "badges", title: "MA & Trend", badges: [
|
||||
{ label: "SMA 50", valuePath: "technical_indicators.sma_50", trendPath: "technical_indicators.trend_directions.sma_50" },
|
||||
{ label: "SMA 200", valuePath: "technical_indicators.sma_200", trendPath: "technical_indicators.trend_directions.sma_200" },
|
||||
{ label: "EMA 10", valuePath: "technical_indicators.ema_10", trendPath: "technical_indicators.trend_directions.ema_10" }
|
||||
], trendColors: { BULLISH: "green", BEARISH: "red", NEUTRAL: "gray" } },
|
||||
{ id: "atr_chip", type: "kpi_text", title: "ATR (Volatility)", dataPath: "technical_indicators.atr" },
|
||||
{ id: "strategy_ladder", type: "price_ladder", title: "Entry, Stop, Targets", entryStrategyPath: "investment_strategy.position_sizing.entry_strategy", stopPricePath: "investment_strategy.risk_management.initial_stop_loss", stopPercentPath: "investment_strategy.risk_management.stop_loss_percent", targetsPath: "investment_strategy.profit_targets", targetMapping: { price: "target_price", label: "action" }, currentPricePath: "financial_data.current_price" },
|
||||
{ id: "position_sizing", type: "chips", title: "Position Sizing", items: [
|
||||
{ label: "Total Allocation", valuePath: "investment_strategy.position_sizing.total_allocation_percent" },
|
||||
{ label: "Tranche 1", valuePath: "investment_strategy.position_sizing.tranche_1_percent" },
|
||||
{ label: "Tranche 2", valuePath: "investment_strategy.position_sizing.tranche_2_percent" }
|
||||
] },
|
||||
{ id: "monitoring_points", type: "bullet_list", title: "Monitoring Points", itemsPath: "investment_strategy.monitoring_points" },
|
||||
{ id: "analyst_target_vs_price", type: "bar_compare", title: "Analyst Target vs Current", series: [ { label: "Current", valuePath: "financial_data.current_price" }, { label: "Target", valuePath: "financial_data.analyst_data.price_target" } ], yFormat: "currency" },
|
||||
{ id: "report_technical", type: "expandable_report", titlePath: "text_content.market_report.title", contentPath: "text_content.market_report.content", bulletsPath: "text_content.market_report.key_takeaways" },
|
||||
{ id: "report_sentiment", type: "expandable_report", titlePath: "text_content.sentiment_report.title", contentPath: "text_content.sentiment_report.content", bulletsPath: "text_content.sentiment_report.recent_developments" },
|
||||
{ id: "report_fundamentals", type: "expandable_report", titlePath: "text_content.fundamentals_report.title", contentPath: "text_content.fundamentals_report.content", bulletsPath: "text_content.fundamentals_report.financial_highlights" },
|
||||
{ id: "report_investment_plan", type: "expandable_report", titlePath: "text_content.investment_plan_full.title", contentPath: "text_content.investment_plan_full.content" },
|
||||
{ id: "debate_summary", type: "debate_viewer", bullPath: "debate_summary.bull_key_points", bearPath: "debate_summary.bear_key_points", neutralPath: "debate_summary.neutral_perspective", finalPath: "debate_summary.final_decision_rationale" }
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
// Ensure we have a widgets_config; if missing, build a sensible default
|
||||
const transformedWithConfig = {
|
||||
...transformedData,
|
||||
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
|
||||
};
|
||||
|
||||
// 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 = React.useMemo(() => ((data?.debate_summary as any) || {}), [data]);
|
||||
const txt = React.useMemo(() => ((data?.text_content as any) || {}), [data]);
|
||||
|
||||
const trends = ti?.trend_directions || {};
|
||||
const fmt = (v: any, d = 0) => {
|
||||
const n = Number(v);
|
||||
if (v === null || v === undefined || Number.isNaN(n)) return '-';
|
||||
return Number.isFinite(n) ? n.toFixed(d) : String(v);
|
||||
};
|
||||
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 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="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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Render minimalist dashboard with all keys from the transformed JSON
|
||||
return <MinimalTransformedDashboard data={transformedWithConfig} />;
|
||||
};
|
||||
|
||||
export default TransformedDataAdapter;
|
||||
export type { TransformedAnalysisData, LegacyTradingAgentsResult };
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Custom styles for the TradingAgents app */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
/* Custom primary colors for Tailwind */
|
||||
:root {
|
||||
--color-primary-50: #eff6ff;
|
||||
--color-primary-100: #dbeafe;
|
||||
--color-primary-200: #bfdbfe;
|
||||
--color-primary-300: #93c5fd;
|
||||
--color-primary-400: #60a5fa;
|
||||
--color-primary-500: #3b82f6;
|
||||
--color-primary-600: #2563eb;
|
||||
--color-primary-700: #1d4ed8;
|
||||
--color-primary-800: #1e40af;
|
||||
--color-primary-900: #1e3a8a;
|
||||
}
|
||||
|
||||
/* Success colors */
|
||||
:root {
|
||||
--color-success-50: #f0fdf4;
|
||||
--color-success-100: #dcfce7;
|
||||
--color-success-200: #bbf7d0;
|
||||
--color-success-300: #86efac;
|
||||
--color-success-400: #4ade80;
|
||||
--color-success-500: #22c55e;
|
||||
--color-success-600: #16a34a;
|
||||
--color-success-700: #15803d;
|
||||
--color-success-800: #166534;
|
||||
--color-success-900: #14532d;
|
||||
}
|
||||
|
||||
/* Danger colors */
|
||||
:root {
|
||||
--color-danger-50: #fef2f2;
|
||||
--color-danger-100: #fee2e2;
|
||||
--color-danger-200: #fecaca;
|
||||
--color-danger-300: #fca5a5;
|
||||
--color-danger-400: #f87171;
|
||||
--color-danger-500: #ef4444;
|
||||
--color-danger-600: #dc2626;
|
||||
--color-danger-700: #b91c1c;
|
||||
--color-danger-800: #991b1b;
|
||||
--color-danger-900: #7f1d1d;
|
||||
}
|
||||
|
||||
/* Warning colors */
|
||||
:root {
|
||||
--color-warning-50: #fffbeb;
|
||||
--color-warning-100: #fef3c7;
|
||||
--color-warning-200: #fde68a;
|
||||
--color-warning-300: #fcd34d;
|
||||
--color-warning-400: #fbbf24;
|
||||
--color-warning-500: #f59e0b;
|
||||
--color-warning-600: #d97706;
|
||||
--color-warning-700: #b45309;
|
||||
--color-warning-800: #92400e;
|
||||
--color-warning-900: #78350f;
|
||||
}
|
||||
|
||||
/* Custom animations */
|
||||
@keyframes pulse-slow {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse-slow {
|
||||
animation: pulse-slow 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
/* Loading spinner improvements */
|
||||
.loading-spinner {
|
||||
border-top-color: transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
@ -0,0 +1,561 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
|
||||
import NewsFeedWidget from '../components/NewsFeedWidget.tsx';
|
||||
|
||||
interface AnalysisData {
|
||||
symbol: string;
|
||||
currentPrice: number;
|
||||
marketCap: string;
|
||||
psRatio: number;
|
||||
forwardPE: number;
|
||||
targetPrice: number;
|
||||
rsi: number;
|
||||
macd: number;
|
||||
ma50: number;
|
||||
ma200: number;
|
||||
stopLoss: number;
|
||||
profitTarget1: number;
|
||||
profitTarget2: number;
|
||||
riskLevel: number;
|
||||
bullArguments: string[];
|
||||
bearArguments: string[];
|
||||
neutralPerspective: string;
|
||||
earningsDate: string;
|
||||
sentimentScore: number;
|
||||
insiderOwnership: number;
|
||||
institutionalOwnership: number;
|
||||
retailOwnership: number;
|
||||
finalDecision: string;
|
||||
decisionReasoning: string;
|
||||
}
|
||||
|
||||
interface AnalysisWidgetsProps {
|
||||
data: AnalysisData;
|
||||
rawData?: any;
|
||||
onBackWidget?: () => void;
|
||||
onRefreshWidget?: () => void;
|
||||
}
|
||||
|
||||
const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBackWidget, onRefreshWidget }) => {
|
||||
const [activeTab, setActiveTab] = useState('bull');
|
||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set());
|
||||
const [positionSize, setPositionSize] = useState(10000);
|
||||
const [portfolioAllocation, setPortfolioAllocation] = useState(4);
|
||||
|
||||
const handleBack = () => {
|
||||
if (onBackWidget) return onBackWidget();
|
||||
if (typeof window !== 'undefined' && window.history) window.history.back();
|
||||
};
|
||||
const handleRefresh = () => {
|
||||
if (onRefreshWidget) return onRefreshWidget();
|
||||
if (typeof window !== 'undefined') window.location.reload();
|
||||
};
|
||||
|
||||
const WidgetHeader: React.FC<{ title: string }> = ({ title }) => (
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Back"
|
||||
title="Back"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRefresh}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
⟳
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Mock price data for chart
|
||||
const priceData = [
|
||||
{ date: '2024-07-01', price: 4.20, volume: 1200000 },
|
||||
{ date: '2024-07-08', price: 4.45, volume: 1500000 },
|
||||
{ date: '2024-07-15', price: 4.80, volume: 1800000 },
|
||||
{ date: '2024-07-22', price: 5.20, volume: 2100000 },
|
||||
{ date: '2024-07-26', price: 5.81, volume: 2500000 },
|
||||
];
|
||||
|
||||
const ownershipData = [
|
||||
{ name: 'Insider', value: data.insiderOwnership, color: '#8884d8' },
|
||||
{ name: 'Institutional', value: data.institutionalOwnership, color: '#82ca9d' },
|
||||
{ name: 'Retail', value: data.retailOwnership, color: '#ffc658' },
|
||||
];
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
const newExpanded = new Set(expandedSections);
|
||||
if (newExpanded.has(section)) {
|
||||
newExpanded.delete(section);
|
||||
} else {
|
||||
newExpanded.add(section);
|
||||
}
|
||||
setExpandedSections(newExpanded);
|
||||
};
|
||||
|
||||
const calculatePosition = () => {
|
||||
const allocation = (portfolioAllocation / 100) * positionSize;
|
||||
const shares = Math.floor(allocation / data.currentPrice);
|
||||
return { allocation, shares };
|
||||
};
|
||||
|
||||
const getRiskColor = (level: number) => {
|
||||
if (level < 30) return 'text-green-600';
|
||||
if (level < 70) return 'text-yellow-600';
|
||||
return 'text-red-600';
|
||||
};
|
||||
|
||||
const getTrendColor = (current: number, reference: number) => {
|
||||
return current > reference ? 'text-green-600' : 'text-red-600';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
{data.symbol} Analysis Dashboard
|
||||
</h1>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-2xl font-semibold text-green-600">
|
||||
${data.currentPrice.toFixed(2)}
|
||||
</span>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
||||
data.finalDecision === 'BUY' ? 'bg-green-100 text-green-800' :
|
||||
data.finalDecision === 'SELL' ? 'bg-red-100 text-red-800' :
|
||||
'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{data.finalDecision}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Core Financial Widgets */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* Stock Price Chart */}
|
||||
<div className="lg:col-span-2 bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Stock Price Chart" />
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={priceData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="price" stroke="#2563eb" strokeWidth={2} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-600">Daily High:</span>
|
||||
<span className="ml-2 font-medium">${(data.currentPrice * 1.05).toFixed(2)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600">Daily Low:</span>
|
||||
<span className="ml-2 font-medium">${(data.currentPrice * 0.95).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Technical Indicators Dashboard */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Technical Indicators" />
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">RSI</span>
|
||||
<span className={`font-medium ${data.rsi < 30 ? 'text-green-600' : data.rsi > 70 ? 'text-red-600' : 'text-yellow-600'}`}>
|
||||
{data.rsi.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">MACD</span>
|
||||
<span className={`font-medium ${data.macd > 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{data.macd.toFixed(3)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">50-day MA</span>
|
||||
<span className={`font-medium ${getTrendColor(data.currentPrice, data.ma50)}`}>
|
||||
${data.ma50.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">200-day MA</span>
|
||||
<span className={`font-medium ${getTrendColor(data.currentPrice, data.ma200)}`}>
|
||||
${data.ma200.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Key Metrics Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h4 className="text-sm font-medium text-gray-600 mb-2">Market Cap</h4>
|
||||
<p className="text-2xl font-bold text-gray-900">{data.marketCap}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h4 className="text-sm font-medium text-gray-600 mb-2">P/S Ratio</h4>
|
||||
<p className="text-2xl font-bold text-gray-900">{data.psRatio.toFixed(1)}x</p>
|
||||
<p className="text-sm text-gray-500">vs 0.8x fair value</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h4 className="text-sm font-medium text-gray-600 mb-2">Forward P/E</h4>
|
||||
<p className="text-2xl font-bold text-gray-900">{data.forwardPE.toFixed(2)}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h4 className="text-sm font-medium text-gray-600 mb-2">Price Target</h4>
|
||||
<p className="text-2xl font-bold text-gray-900">${data.targetPrice.toFixed(2)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analysis & Decision Widgets */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
{/* Bull vs Bear Debate Viewer */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Bull vs Bear Debate" />
|
||||
<div className="flex space-x-1 mb-4">
|
||||
<button
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium ${
|
||||
activeTab === 'bull' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'
|
||||
}`}
|
||||
onClick={() => setActiveTab('bull')}
|
||||
>
|
||||
Bull Case
|
||||
</button>
|
||||
<button
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium ${
|
||||
activeTab === 'bear' ? 'bg-red-100 text-red-800' : 'bg-gray-100 text-gray-600'
|
||||
}`}
|
||||
onClick={() => setActiveTab('bear')}
|
||||
>
|
||||
Bear Case
|
||||
</button>
|
||||
<button
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium ${
|
||||
activeTab === 'neutral' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-600'
|
||||
}`}
|
||||
onClick={() => setActiveTab('neutral')}
|
||||
>
|
||||
Neutral
|
||||
</button>
|
||||
</div>
|
||||
<div className="min-h-[200px]">
|
||||
{activeTab === 'bull' && (
|
||||
<ul className="space-y-2">
|
||||
{data.bullArguments.map((arg, index) => (
|
||||
<li key={index} className="flex items-start">
|
||||
<span className="text-green-500 mr-2">•</span>
|
||||
<span className="text-sm">{arg}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{activeTab === 'bear' && (
|
||||
<ul className="space-y-2">
|
||||
{data.bearArguments.map((arg, index) => (
|
||||
<li key={index} className="flex items-start">
|
||||
<span className="text-red-500 mr-2">•</span>
|
||||
<span className="text-sm">{arg}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{activeTab === 'neutral' && (
|
||||
<p className="text-sm text-gray-700">{data.neutralPerspective}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Investment Plan Timeline */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Investment Plan Timeline" />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-blue-500 rounded-full mr-4"></div>
|
||||
<div>
|
||||
<p className="font-medium">Entry Point</p>
|
||||
<p className="text-sm text-gray-600">Current: ${data.currentPrice.toFixed(2)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-red-500 rounded-full mr-4"></div>
|
||||
<div>
|
||||
<p className="font-medium">Stop Loss</p>
|
||||
<p className="text-sm text-gray-600">${data.stopLoss.toFixed(2)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-green-500 rounded-full mr-4"></div>
|
||||
<div>
|
||||
<p className="font-medium">Profit Target 1</p>
|
||||
<p className="text-sm text-gray-600">${data.profitTarget1.toFixed(2)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-green-600 rounded-full mr-4"></div>
|
||||
<div>
|
||||
<p className="font-medium">Profit Target 2</p>
|
||||
<p className="text-sm text-gray-600">${data.profitTarget2.toFixed(2)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive Decision Tools */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* Risk Assessment Gauge */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Risk Assessment" />
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<div className="relative w-32 h-32">
|
||||
<svg className="w-32 h-32 transform -rotate-90" viewBox="0 0 36 36">
|
||||
<path
|
||||
d="M18 2.0845
|
||||
a 15.9155 15.9155 0 0 1 0 31.831
|
||||
a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||
fill="none"
|
||||
stroke="#e5e7eb"
|
||||
strokeWidth="3"
|
||||
/>
|
||||
<path
|
||||
d="M18 2.0845
|
||||
a 15.9155 15.9155 0 0 1 0 31.831
|
||||
a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||
fill="none"
|
||||
stroke={data.riskLevel < 30 ? '#10b981' : data.riskLevel < 70 ? '#f59e0b' : '#ef4444'}
|
||||
strokeWidth="3"
|
||||
strokeDasharray={`${data.riskLevel}, 100`}
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className={`text-2xl font-bold ${getRiskColor(data.riskLevel)}`}>
|
||||
{data.riskLevel}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center text-sm text-gray-600">Overall Risk Level</p>
|
||||
</div>
|
||||
|
||||
{/* Position Calculator */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Position Calculator" />
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Portfolio Size ($)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={positionSize}
|
||||
onChange={(e) => setPositionSize(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Allocation (%)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={portfolioAllocation}
|
||||
onChange={(e) => setPortfolioAllocation(Number(e.target.value))}
|
||||
min="1"
|
||||
max="100"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2 border-t">
|
||||
<p className="text-sm text-gray-600">Recommended Position:</p>
|
||||
<p className="font-medium">${calculatePosition().allocation.toFixed(2)}</p>
|
||||
<p className="text-sm text-gray-600">{calculatePosition().shares} shares</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sentiment Thermometer */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Sentiment Thermometer" />
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<div className="w-8 h-32 bg-gray-200 rounded-full relative">
|
||||
<div
|
||||
className={`absolute bottom-0 w-full rounded-full ${
|
||||
data.sentimentScore > 70 ? 'bg-green-500' :
|
||||
data.sentimentScore > 30 ? 'bg-yellow-500' : 'bg-red-500'
|
||||
}`}
|
||||
style={{ height: `${data.sentimentScore}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-2xl font-bold">{data.sentimentScore}%</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
{data.sentimentScore > 70 ? 'Bullish' :
|
||||
data.sentimentScore > 30 ? 'Neutral' : 'Bearish'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Monitoring & Alerts */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* News Feed Widget */}
|
||||
<div className="lg:col-span-1">
|
||||
<NewsFeedWidget symbol={data.symbol} maxItems={8} onBack={handleBack} onRefresh={handleRefresh} />
|
||||
</div>
|
||||
|
||||
{/* Earnings Countdown Timer */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Earnings Countdown" />
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold text-blue-600 mb-2">{data.earningsDate}</p>
|
||||
<p className="text-sm text-gray-600 mb-4">Next Earnings Call</p>
|
||||
<div className="text-left">
|
||||
<p className="font-medium mb-2">Key Focus Areas:</p>
|
||||
<ul className="text-sm text-gray-600 space-y-1">
|
||||
<li>• Revenue growth trajectory</li>
|
||||
<li>• Margin expansion</li>
|
||||
<li>• Forward guidance</li>
|
||||
<li>• Market share gains</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ownership Structure Pie Chart */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Ownership Structure" />
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={ownershipData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
dataKey="value"
|
||||
label={({ name, value }) => `${name}: ${value.toFixed(2)}%`}
|
||||
>
|
||||
{ownershipData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decision History Tracker */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6 mb-8">
|
||||
<WidgetHeader title="Decision History Tracker" />
|
||||
<div className="relative">
|
||||
<div className="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-300"></div>
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{ date: '2024-07-20', decision: 'HOLD', reasoning: 'Initial analysis pending earnings data', color: 'yellow' },
|
||||
{ date: '2024-07-22', decision: 'HOLD', reasoning: 'Technical indicators mixed, awaiting clearer signals', color: 'yellow' },
|
||||
{ date: '2024-07-25', decision: 'BUY', reasoning: 'Strong fundamentals confirmed, positive technical momentum', color: 'green' },
|
||||
{ date: '2024-07-26', decision: 'BUY', reasoning: 'Final recommendation based on comprehensive analysis', color: 'green' }
|
||||
].map((item, index) => (
|
||||
<div key={index} className="relative flex items-start">
|
||||
<div className={`absolute left-0 w-8 h-8 rounded-full border-4 border-white ${
|
||||
item.color === 'green' ? 'bg-green-500' :
|
||||
item.color === 'red' ? 'bg-red-500' : 'bg-yellow-500'
|
||||
} shadow-md`}></div>
|
||||
<div className="ml-12">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
item.decision === 'BUY' ? 'bg-green-100 text-green-800' :
|
||||
item.decision === 'SELL' ? 'bg-red-100 text-red-800' :
|
||||
'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{item.decision}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">{item.date}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700">{item.reasoning}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Information Widgets */}
|
||||
<div className="space-y-6">
|
||||
{/* Executive Summary Box */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Executive Summary" />
|
||||
<div className="bg-blue-50 border-l-4 border-blue-400 p-4">
|
||||
<p className="text-sm">
|
||||
<strong>Final Recommendation: {data.finalDecision}</strong> - {data.decisionReasoning}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Text Sections */}
|
||||
{[
|
||||
{ key: 'market-report', title: 'Market Report Summary', content: 'Detailed technical analysis with key indicators and price movement analysis...' },
|
||||
{ key: 'sentiment-report', title: 'Sentiment Report Card', content: 'Complete company sentiment analysis including recent news and social media sentiment...' },
|
||||
{ key: 'fundamentals', title: 'Fundamentals Report Panel', content: 'Company overview, financial metrics, analyst insights, and key takeaways...' },
|
||||
{ key: 'macro-news', title: 'Macroeconomic News Brief', content: 'Trade policies, Federal Reserve developments, and market dynamics...' },
|
||||
{ key: 'debate-transcript', title: 'Investment Debate Transcript', content: 'Complete bull and bear analyst arguments with neutral perspective...' },
|
||||
{ key: 'risk-analysis', title: 'Risk Analysis Discussion', content: 'Full risky vs safe analyst debate with neutral commentary...' },
|
||||
{ key: 'investment-plan', title: 'Final Investment Plan Document', content: 'Comprehensive investment strategy with position sizing and risk management...' },
|
||||
].map((section) => (
|
||||
<div key={section.key} className="bg-white rounded-lg shadow-md">
|
||||
<button
|
||||
className="w-full px-6 py-4 text-left flex justify-between items-center hover:bg-gray-50"
|
||||
onClick={() => toggleSection(section.key)}
|
||||
>
|
||||
<h3 className="text-lg font-semibold">{section.title}</h3>
|
||||
<span className="text-gray-400">
|
||||
{expandedSections.has(section.key) ? '−' : '+'}
|
||||
</span>
|
||||
</button>
|
||||
{expandedSections.has(section.key) && (
|
||||
<div className="px-6 pb-6">
|
||||
<p className="text-gray-700">{section.content}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Raw Data Viewer */}
|
||||
{rawData && (
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<WidgetHeader title="Detailed Analysis Data" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{Object.entries(rawData).map(([key, value]) => (
|
||||
<div key={key} className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 capitalize">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</label>
|
||||
<textarea
|
||||
value={typeof value === 'object' && value !== null ? JSON.stringify(value, null, 2) : String(value || '')}
|
||||
readOnly
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm font-mono"
|
||||
rows={typeof value === 'object' && value !== null ? Math.min(10, JSON.stringify(value, null, 2).split('\n').length) : 3}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalysisWidgets;
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Play, Database, TrendingUp, Clock } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const [companies, setCompanies] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCompanies();
|
||||
}, []);
|
||||
|
||||
const fetchCompanies = async () => {
|
||||
try {
|
||||
const response = await axios.get('/results/companies');
|
||||
setCompanies(response.data.companies);
|
||||
} catch (error) {
|
||||
console.error('Error fetching companies:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Trading Analysis Dashboard</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Execute trading analysis and view historical results for stock predictions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<Link
|
||||
to="/run-analysis"
|
||||
className="bg-primary-600 hover:bg-primary-700 text-white p-6 rounded-lg shadow-md transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Play className="h-8 w-8 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">Run New Analysis</h3>
|
||||
<p className="text-primary-100">Execute trading pipeline for any stock symbol</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/results"
|
||||
className="bg-success-600 hover:bg-success-700 text-white p-6 rounded-lg shadow-md transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Database className="h-8 w-8 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">View Agent Outputs</h3>
|
||||
<p className="text-success-100">Browse historical analysis results</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Recent Results */}
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-medium text-gray-900">Recent Analysis Results</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{loading ? (
|
||||
<div className="text-center py-4">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p className="mt-2 text-gray-500">Loading results...</p>
|
||||
</div>
|
||||
) : companies.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{companies.slice(0, 6).map((company) => (
|
||||
<div key={company.symbol} className="border rounded-lg p-4 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="font-semibold text-gray-900">{company.symbol}</h3>
|
||||
<TrendingUp className="h-4 w-4 text-success-500" />
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
<div className="flex items-center mb-1">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Latest: {company.latest_analysis}
|
||||
</div>
|
||||
<div>Total analyses: {company.total_analyses}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Database className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-500">No analysis results found</p>
|
||||
<Link
|
||||
to="/run-analysis"
|
||||
className="mt-2 inline-flex items-center text-primary-600 hover:text-primary-500"
|
||||
>
|
||||
Run your first analysis
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowLeft, Calendar, FileText, Download, BarChart3 } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const ResultDetail: React.FC = () => {
|
||||
const { symbol, date } = useParams<{ symbol: string; date: string }>();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (symbol && date) {
|
||||
fetchResultDetail(symbol, date);
|
||||
}
|
||||
}, [symbol, date]);
|
||||
|
||||
const fetchResultDetail = async (sym: string, dt: string) => {
|
||||
try {
|
||||
const response = await axios.get(`/results/${sym}/${dt}`);
|
||||
setData(response.data);
|
||||
} catch (error: any) {
|
||||
setError(error.response?.data?.detail || 'Failed to load result details');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const renderDataVisualization = (analysisData: any) => {
|
||||
// Try to extract meaningful data for visualization
|
||||
if (!analysisData || typeof analysisData !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look for common data patterns in trading analysis
|
||||
const keys = Object.keys(analysisData);
|
||||
const timeSeriesKeys = keys.filter(key =>
|
||||
key.toLowerCase().includes('price') ||
|
||||
key.toLowerCase().includes('volume') ||
|
||||
key.toLowerCase().includes('time') ||
|
||||
key.toLowerCase().includes('date')
|
||||
);
|
||||
|
||||
if (timeSeriesKeys.length > 0) {
|
||||
// Create a simple visualization if we have time series data
|
||||
const labels = Array.from({ length: 10 }, (_, i) => `Day ${i + 1}`);
|
||||
const chartData = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Analysis Trend',
|
||||
data: Array.from({ length: 10 }, () => Math.random() * 100),
|
||||
borderColor: 'rgb(59, 130, 246)',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top' as const,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: `${symbol} Analysis Visualization`,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
|
||||
<BarChart3 className="h-5 w-5 mr-2" />
|
||||
Data Visualization
|
||||
</h3>
|
||||
<div className="h-64">
|
||||
<Line data={chartData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderDataSection = (title: string, content: any, maxHeight = '400px') => {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">{title}</h3>
|
||||
<div
|
||||
className="bg-gray-50 rounded-lg p-4 overflow-auto font-mono text-sm"
|
||||
style={{ maxHeight }}
|
||||
>
|
||||
<pre className="whitespace-pre-wrap">
|
||||
{typeof content === 'string' ? content : JSON.stringify(content, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="text-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-500">Loading analysis details...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="text-center py-12">
|
||||
<FileText className="h-16 w-16 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Error Loading Result</h3>
|
||||
<p className="text-red-600 mb-4">{error}</p>
|
||||
<Link
|
||||
to="/results"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Results
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
to="/results"
|
||||
className="inline-flex items-center text-primary-600 hover:text-primary-500 mb-4"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Results
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold text-gray-900">
|
||||
{symbol} Analysis - {date}
|
||||
</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Detailed view of trading analysis results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Metadata */}
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||
<h2 className="text-lg font-medium text-gray-900 mb-4">Analysis Metadata</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
<Calendar className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<span className="font-medium">Analysis Date</span>
|
||||
</div>
|
||||
<p className="text-gray-900">{date}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
<FileText className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<span className="font-medium">File Size</span>
|
||||
</div>
|
||||
<p className="text-gray-900">{formatFileSize(data?.metadata?.file_size || 0)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
<Download className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<span className="font-medium">Last Modified</span>
|
||||
</div>
|
||||
<p className="text-gray-900">
|
||||
{data?.metadata?.modified_at ? formatDate(data.metadata.modified_at) : 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Visualization */}
|
||||
{data?.data && renderDataVisualization(data.data)}
|
||||
|
||||
{/* Analysis Data */}
|
||||
{data?.data && (
|
||||
<>
|
||||
{/* Summary Section */}
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Analysis Summary</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<span className="font-medium text-gray-700">Symbol:</span>
|
||||
<p className="text-gray-900">{symbol}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-700">Data Keys:</span>
|
||||
<p className="text-gray-900">
|
||||
{Object.keys(data.data).length} main sections
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<span className="font-medium text-gray-700">Available Sections:</span>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{Object.keys(data.data).map((key) => (
|
||||
<span
|
||||
key={key}
|
||||
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800"
|
||||
>
|
||||
{key}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Detailed Data Sections */}
|
||||
{Object.entries(data.data).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
{renderDataSection(`${key} Data`, value)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Raw Data */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Raw Data</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
const dataStr = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${symbol}_${date}_analysis.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
className="inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
<Download className="h-3 w-3 mr-1" />
|
||||
Download JSON
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-4 overflow-auto font-mono text-sm max-h-96">
|
||||
<pre className="whitespace-pre-wrap">
|
||||
{JSON.stringify(data, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResultDetail;
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Play, Settings, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const RunAnalysis: React.FC = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
symbol: '',
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
});
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [jobId, setJobId] = useState<string | null>(null);
|
||||
const [jobStatus, setJobStatus] = useState<any>(null);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!formData.symbol.trim()) {
|
||||
toast.error('Please enter a stock symbol');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRunning(true);
|
||||
try {
|
||||
const response = await axios.post('/analysis/start', {
|
||||
symbol: formData.symbol.toUpperCase(),
|
||||
date: formData.date,
|
||||
});
|
||||
|
||||
setJobId(response.data.job_id);
|
||||
toast.success('Analysis started successfully!');
|
||||
pollJobStatus(response.data.job_id);
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.detail || 'Failed to start analysis');
|
||||
setIsRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const pollJobStatus = async (id: string) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const response = await axios.get(`/analysis/status/${id}`);
|
||||
setJobStatus(response.data);
|
||||
|
||||
if (response.data.status === 'completed') {
|
||||
clearInterval(interval);
|
||||
setIsRunning(false);
|
||||
toast.success('Analysis completed successfully!');
|
||||
} else if (response.data.status === 'failed') {
|
||||
clearInterval(interval);
|
||||
setIsRunning(false);
|
||||
toast.error(`Analysis failed: ${response.data.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(interval);
|
||||
setIsRunning(false);
|
||||
toast.error('Error checking job status');
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Run Trading Analysis</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Execute the TradingAgents pipeline to generate predictions for any stock symbol
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="symbol" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Stock Symbol
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="symbol"
|
||||
value={formData.symbol}
|
||||
onChange={(e) => setFormData({ ...formData, symbol: e.target.value.toUpperCase() })}
|
||||
placeholder="e.g., NVDA, AAPL, TSLA"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
|
||||
disabled={isRunning}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="date" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Analysis Date
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
value={formData.date}
|
||||
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
|
||||
disabled={isRunning}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isRunning}
|
||||
className="w-full flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
Running Analysis...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
Start Analysis
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Job Status */}
|
||||
{jobStatus && (
|
||||
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center mb-2">
|
||||
{jobStatus.status === 'completed' && <CheckCircle className="h-5 w-5 text-success-500 mr-2" />}
|
||||
{jobStatus.status === 'failed' && <AlertCircle className="h-5 w-5 text-danger-500 mr-2" />}
|
||||
{jobStatus.status === 'running' && (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary-600 mr-2"></div>
|
||||
)}
|
||||
<span className="font-medium capitalize">{jobStatus.status}</span>
|
||||
</div>
|
||||
{jobStatus.progress && (
|
||||
<p className="text-sm text-gray-600 mb-2">{jobStatus.progress}</p>
|
||||
)}
|
||||
{jobStatus.result && (
|
||||
<div className="mt-4 p-3 bg-white rounded border">
|
||||
<h4 className="font-medium mb-2">Analysis Result:</h4>
|
||||
<pre className="text-xs bg-gray-100 p-2 rounded overflow-x-auto">
|
||||
{JSON.stringify(jobStatus.result, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RunAnalysis;
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Calendar, FileText, TrendingUp, Clock, Eye } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
|
||||
interface Company {
|
||||
symbol: string;
|
||||
latest_analysis: string;
|
||||
total_analyses: number;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
date: string;
|
||||
filename: string;
|
||||
file_size: number;
|
||||
modified_at: string;
|
||||
preview?: {
|
||||
keys: string[];
|
||||
size: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const ViewResults: React.FC = () => {
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
const [selectedCompany, setSelectedCompany] = useState<string>('');
|
||||
const [results, setResults] = useState<Result[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingResults, setLoadingResults] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCompanies();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCompany) {
|
||||
fetchCompanyResults(selectedCompany);
|
||||
}
|
||||
}, [selectedCompany]);
|
||||
|
||||
const fetchCompanies = async () => {
|
||||
try {
|
||||
const response = await axios.get('/results/companies');
|
||||
setCompanies(response.data.companies);
|
||||
if (response.data.companies.length > 0) {
|
||||
setSelectedCompany(response.data.companies[0].symbol);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching companies:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCompanyResults = async (symbol: string) => {
|
||||
setLoadingResults(true);
|
||||
try {
|
||||
const response = await axios.get(`/results/${symbol}`);
|
||||
setResults(response.data.results);
|
||||
} catch (error) {
|
||||
console.error('Error fetching results:', error);
|
||||
setResults([]);
|
||||
} finally {
|
||||
setLoadingResults(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="text-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-500">Loading analysis results...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-4 py-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Analysis Results</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Browse and view historical trading analysis results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{companies.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<FileText className="h-16 w-16 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No Results Found</h3>
|
||||
<p className="text-gray-500 mb-4">No analysis results are available yet.</p>
|
||||
<Link
|
||||
to="/run-analysis"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700"
|
||||
>
|
||||
Run Your First Analysis
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{/* Company Selector */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-white rounded-lg shadow p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Companies</h3>
|
||||
<div className="space-y-2">
|
||||
{companies.map((company) => (
|
||||
<button
|
||||
key={company.symbol}
|
||||
onClick={() => setSelectedCompany(company.symbol)}
|
||||
className={`w-full text-left p-3 rounded-lg border transition-colors ${
|
||||
selectedCompany === company.symbol
|
||||
? 'bg-primary-50 border-primary-200 text-primary-900'
|
||||
: 'bg-gray-50 border-gray-200 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">{company.symbol}</span>
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
{company.total_analyses} analyses
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results List */}
|
||||
<div className="lg:col-span-3">
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Results for {selectedCompany}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{loadingResults ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p className="mt-2 text-gray-500">Loading results...</p>
|
||||
</div>
|
||||
) : results.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<FileText className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-500">No results found for {selectedCompany}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{results.map((result) => (
|
||||
<div
|
||||
key={result.date}
|
||||
className="border rounded-lg p-4 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<span className="font-medium text-gray-900">
|
||||
Analysis: {result.date}
|
||||
</span>
|
||||
</div>
|
||||
<Link
|
||||
to={`/results/${selectedCompany}/${result.date}`}
|
||||
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-primary-700 bg-primary-100 hover:bg-primary-200"
|
||||
>
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
View Details
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-gray-600">
|
||||
<div>
|
||||
<span className="font-medium">File Size:</span>
|
||||
<div>{formatFileSize(result.file_size)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Modified:</span>
|
||||
<div>{formatDate(result.modified_at)}</div>
|
||||
</div>
|
||||
{result.preview && (
|
||||
<>
|
||||
<div>
|
||||
<span className="font-medium">Data Keys:</span>
|
||||
<div>{result.preview.keys.length} keys</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Data Size:</span>
|
||||
<div>{result.preview.size} chars</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{result.error && (
|
||||
<div className="col-span-2 md:col-span-4">
|
||||
<span className="font-medium text-red-600">Error:</span>
|
||||
<div className="text-red-600">{result.error}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewResults;
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:8000';
|
||||
|
||||
export interface TransformedAnalysis {
|
||||
// Raw filename on disk (not unique across companies)
|
||||
filename: string;
|
||||
// Analysis date (YYYY-MM-DD)
|
||||
date: string;
|
||||
modified_at?: string;
|
||||
file_size?: number;
|
||||
preview?: {
|
||||
final_recommendation: string;
|
||||
confidence_level: string;
|
||||
current_price: number;
|
||||
};
|
||||
error?: string;
|
||||
// Ticker symbol
|
||||
symbol: string;
|
||||
// User-facing label
|
||||
displayName: string;
|
||||
// Globally unique id to disambiguate duplicates across companies (symbol|date)
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface TransformedData {
|
||||
metadata: {
|
||||
company_ticker: string;
|
||||
analysis_date: string;
|
||||
final_recommendation: string;
|
||||
confidence_level: string;
|
||||
current_price: number;
|
||||
target_price: number;
|
||||
risk_level: string;
|
||||
time_horizon: string;
|
||||
};
|
||||
financial_data: {
|
||||
current_price: number;
|
||||
target_price: number;
|
||||
price_change_1d: number;
|
||||
price_change_1w: number;
|
||||
price_change_1m: number;
|
||||
market_cap: number;
|
||||
volume: number;
|
||||
pe_ratio: number;
|
||||
revenue: number;
|
||||
profit_margin: number;
|
||||
debt_to_equity: number;
|
||||
roe: number;
|
||||
dividend_yield: number;
|
||||
};
|
||||
technical_indicators: {
|
||||
rsi: number;
|
||||
macd: number;
|
||||
moving_avg_20: number;
|
||||
moving_avg_50: number;
|
||||
bollinger_upper: number;
|
||||
bollinger_lower: number;
|
||||
support_level: number;
|
||||
resistance_level: number;
|
||||
};
|
||||
investment_strategy: {
|
||||
position_size: number;
|
||||
entry_price: number;
|
||||
stop_loss: number;
|
||||
take_profit: number;
|
||||
holding_period: string;
|
||||
risk_reward_ratio: number;
|
||||
};
|
||||
debate_summary: {
|
||||
bull_case: {
|
||||
key_points: string[];
|
||||
strength_score: number;
|
||||
};
|
||||
bear_case: {
|
||||
key_points: string[];
|
||||
strength_score: number;
|
||||
};
|
||||
consensus: string;
|
||||
};
|
||||
text_content: {
|
||||
executive_summary: string;
|
||||
key_takeaways: string[];
|
||||
detailed_analysis: string;
|
||||
risk_factors: string[];
|
||||
catalysts: string[];
|
||||
};
|
||||
widgets_config: {
|
||||
charts_enabled: string[];
|
||||
priority_widgets: string[];
|
||||
display_preferences: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
class TransformedDataService {
|
||||
private baseUrl = API_BASE_URL;
|
||||
private cachedFiles: TransformedAnalysis[] | null = null;
|
||||
private cachedData: Map<string, TransformedData> = new Map();
|
||||
|
||||
private makeCacheKey(symbol: string, date: string) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
|
||||
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);
|
||||
this.cachedFiles = [];
|
||||
}
|
||||
|
||||
return this.cachedFiles;
|
||||
}
|
||||
|
||||
async loadTransformedData(symbol: string, date: string): Promise<TransformedData> {
|
||||
const cacheKey = this.makeCacheKey(symbol, date);
|
||||
if (this.cachedData.has(cacheKey)) {
|
||||
return this.cachedData.get(cacheKey)!;
|
||||
}
|
||||
|
||||
try {
|
||||
const match = date.match(/(\d{4}-\d{2}-\d{2})/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid date format: ${date}`);
|
||||
}
|
||||
|
||||
const files = await this.getAvailableFiles();
|
||||
const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
|
||||
if (!fileEntry) {
|
||||
// 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 responseData = response.data;
|
||||
const data: TransformedData = responseData.data;
|
||||
|
||||
this.validateTransformedData(data);
|
||||
|
||||
this.cachedData.set(cacheKey, data);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error loading transformed data from ${symbol} ${date}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async loadByCompanyAndDate(company: string, date: string): Promise<TransformedData> {
|
||||
const files = await this.getAvailableFiles();
|
||||
const entry = files.find(f => f.symbol === company && f.date === date);
|
||||
if (entry) {
|
||||
return this.loadTransformedData(entry.symbol, entry.date);
|
||||
}
|
||||
|
||||
const response = await axios.get(`${this.baseUrl}/transformed-results/${company}/${date}`);
|
||||
const data: TransformedData = response.data.data;
|
||||
this.validateTransformedData(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async getLatestForCompany(company: string): Promise<TransformedData | null> {
|
||||
const files = await this.getAvailableFiles();
|
||||
const companyFiles = files
|
||||
.filter(f => f.symbol === company)
|
||||
.sort((a, b) => b.date.localeCompare(a.date));
|
||||
|
||||
if (companyFiles.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.loadTransformedData(companyFiles[0].symbol, companyFiles[0].date);
|
||||
}
|
||||
|
||||
async getAvailableCompanies(): Promise<string[]> {
|
||||
const files = await this.getAvailableFiles();
|
||||
const companies = [...new Set(files.map(f => f.symbol))].sort();
|
||||
return companies;
|
||||
}
|
||||
|
||||
async getAvailableDatesForCompany(company: string): Promise<string[]> {
|
||||
const files = await this.getAvailableFiles();
|
||||
const dates = files
|
||||
.filter(f => f.symbol === company)
|
||||
.map(f => f.date)
|
||||
.sort((a, b) => b.localeCompare(a));
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
private validateTransformedData(data: any): void {
|
||||
// Core sections that must exist
|
||||
const requiredSections = [
|
||||
'metadata',
|
||||
'financial_data',
|
||||
'technical_indicators',
|
||||
'investment_strategy'
|
||||
];
|
||||
for (const section of requiredSections) {
|
||||
if (!(section in data)) {
|
||||
throw new Error(`Missing required section: ${section}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize legacy singular widget_config to widgets_config, if present
|
||||
if (!('widgets_config' in data) && ('widget_config' in data)) {
|
||||
data.widgets_config = data.widget_config;
|
||||
}
|
||||
|
||||
// widgets_config is optional; UI will supply a sensible default if absent
|
||||
}
|
||||
|
||||
clearCache(): void {
|
||||
this.cachedFiles = null;
|
||||
this.cachedData.clear();
|
||||
}
|
||||
|
||||
async generateManifest(): Promise<{ files: TransformedAnalysis[] }> {
|
||||
const files = await this.getAvailableFiles();
|
||||
return { files };
|
||||
}
|
||||
|
||||
async searchAnalyses(criteria: {
|
||||
company?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
recommendation?: 'BUY' | 'SELL' | 'HOLD';
|
||||
}): Promise<TransformedAnalysis[]> {
|
||||
const files = await this.getAvailableFiles();
|
||||
|
||||
return files.filter(file => {
|
||||
if (criteria.company && file.symbol !== criteria.company) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (criteria.dateFrom && file.date < criteria.dateFrom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (criteria.dateTo && file.date > criteria.dateTo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async getDataSummary(): Promise<{
|
||||
totalFiles: number;
|
||||
companies: string[];
|
||||
dateRange: { earliest: string; latest: string };
|
||||
companyCounts: Record<string, number>;
|
||||
}> {
|
||||
const files = await this.getAvailableFiles();
|
||||
|
||||
const companies = [...new Set(files.map(f => f.symbol))].sort();
|
||||
const dates = files.map(f => f.date).sort();
|
||||
|
||||
const companyCounts: Record<string, number> = {};
|
||||
files.forEach(file => {
|
||||
const company = file.symbol;
|
||||
companyCounts[company] = (companyCounts[company] || 0) + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
totalFiles: files.length,
|
||||
companies,
|
||||
dateRange: {
|
||||
earliest: dates[0] || '',
|
||||
latest: dates[dates.length - 1] || ''
|
||||
},
|
||||
companyCounts
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const transformedDataService = new TransformedDataService();
|
||||
export default transformedDataService;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
},
|
||||
success: {
|
||||
50: '#f0fdf4',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
},
|
||||
danger: {
|
||||
50: '#fef2f2',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
},
|
||||
warning: {
|
||||
50: '#fffbeb',
|
||||
500: '#f59e0b',
|
||||
600: '#d97706',
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Startup script for TradingAgents Web Application Backend
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
def main():
|
||||
# Change to backend directory
|
||||
backend_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
backend_dir = os.path.join(backend_dir, 'backend')
|
||||
|
||||
print("🚀 Starting TradingAgents Web Application Backend")
|
||||
print(f"📁 Backend directory: {backend_dir}")
|
||||
|
||||
# Check if main.py exists
|
||||
main_py = os.path.join(backend_dir, 'main.py')
|
||||
if not os.path.exists(main_py):
|
||||
print(f"❌ main.py not found at {main_py}")
|
||||
return False
|
||||
|
||||
# Change to backend directory and run
|
||||
os.chdir(backend_dir)
|
||||
|
||||
print("🔧 Installing dependencies...")
|
||||
try:
|
||||
subprocess.run([sys.executable, '-m', 'pip', 'install', 'fastapi', 'uvicorn', 'pydantic', 'python-multipart'],
|
||||
check=True, capture_output=True)
|
||||
print("✅ Dependencies installed")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"⚠️ Warning: Could not install dependencies: {e}")
|
||||
print(" Please install manually: pip install fastapi uvicorn pydantic python-multipart")
|
||||
|
||||
print("🌐 Starting FastAPI server...")
|
||||
print(" Server will be available at: http://localhost:8000")
|
||||
print(" API documentation at: http://localhost:8000/docs")
|
||||
print(" Press Ctrl+C to stop the server")
|
||||
print("-" * 50)
|
||||
|
||||
try:
|
||||
# Run the server
|
||||
subprocess.run([sys.executable, 'main.py'], check=True)
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Server stopped by user")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Server failed to start: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -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