From b4653ca37b7da6e2d26f999bac208a170224be90 Mon Sep 17 00:00:00 2001 From: Andrew Kaszubski Date: Fri, 26 Dec 2025 11:23:29 +1100 Subject: [PATCH] feat(tests): add test fixtures directory with mock data - Fixes #51 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created tests/fixtures/ with FixtureLoader class (14 loader methods) - Added stock_data fixtures: US, CN (with Chinese columns), standardized OHLCV - Added metadata fixtures: 5 analysis examples with datetime parsing - Added report_sections fixtures: 7 complete analyst report sections - Added api_responses fixtures: OpenAI embeddings and error responses - Added configurations fixtures: vendor and LLM provider configs - Created comprehensive README.md (595 lines) documenting fixture usage - Updated docs/testing/writing-tests.md with fixture examples πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 12 + docs/testing/writing-tests.md | 149 +++++ tests/fixtures/README.md | 595 ++++++++++++++++++ tests/fixtures/__init__.py | 592 +++++++++++++++++ .../api_responses/openai_embeddings.json | 199 ++++++ .../configurations/default_config.json | 200 ++++++ .../fixtures/metadata/analysis_metadata.json | 99 +++ .../report_sections/complete_reports.json | 59 ++ .../fixtures/stock_data/cn_market_ohlcv.json | 119 ++++ .../stock_data/standardized_ohlcv.json | 110 ++++ .../fixtures/stock_data/us_market_ohlcv.json | 137 ++++ 11 files changed, 2271 insertions(+) create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/api_responses/openai_embeddings.json create mode 100644 tests/fixtures/configurations/default_config.json create mode 100644 tests/fixtures/metadata/analysis_metadata.json create mode 100644 tests/fixtures/report_sections/complete_reports.json create mode 100644 tests/fixtures/stock_data/cn_market_ohlcv.json create mode 100644 tests/fixtures/stock_data/standardized_ohlcv.json create mode 100644 tests/fixtures/stock_data/us_market_ohlcv.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f9cc58c7..5562669a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Test fixtures directory with centralized mock data (Issue #51) + - FixtureLoader class for loading JSON fixtures with automatic datetime parsing [file:tests/fixtures/__init__.py](tests/fixtures/__init__.py) + - Stock data fixtures: US market OHLCV, Chinese market OHLCV, standardized data [file:tests/fixtures/stock_data/](tests/fixtures/stock_data/) + - Metadata fixtures: Complete analysis, partial analysis, batch analysis, error scenarios [file:tests/fixtures/metadata/analysis_metadata.json](tests/fixtures/metadata/analysis_metadata.json) + - Report section fixtures: Market, sentiment, news, fundamentals, investment plans [file:tests/fixtures/report_sections/complete_reports.json](tests/fixtures/report_sections/complete_reports.json) + - API response fixtures: OpenAI embeddings (single, batch, error responses) [file:tests/fixtures/api_responses/openai_embeddings.json](tests/fixtures/api_responses/openai_embeddings.json) + - Configuration fixtures: Vendor-specific and LLM provider configurations [file:tests/fixtures/configurations/default_config.json](tests/fixtures/configurations/default_config.json) + - Comprehensive FixtureLoader class with convenience functions and edge case support + - DataFrame conversion utilities for stock data with automatic datetime indexing + - UTF-8 encoding support for Chinese market data with column standardization + - Updated docs/testing/writing-tests.md with fixtures section and usage examples [file:docs/testing/writing-tests.md](docs/testing/writing-tests.md) + - Fixture-based testing best practices documentation - DeepSeek API support for LLM provider integration (Issue #41) - DeepSeek provider integration using ChatOpenAI with base_url pointing to DeepSeek API [file:tradingagents/graph/trading_graph.py:105-145](tradingagents/graph/trading_graph.py) - DEEPSEEK_API_KEY environment variable handling with validation and helpful error messages diff --git a/docs/testing/writing-tests.md b/docs/testing/writing-tests.md index 4d0f4a12..b8541674 100644 --- a/docs/testing/writing-tests.md +++ b/docs/testing/writing-tests.md @@ -162,6 +162,155 @@ def test_openrouter_initialization(openrouter_config, mock_env_openrouter): ## Using Fixtures +### Test Fixtures Directory + +TradingAgents provides a comprehensive test fixtures directory (`tests/fixtures/`) with centralized mock data for tests. This eliminates the need to hardcode test data and ensures consistency across your test suite. + +**Fixture Categories**: +- **Stock Data**: US market OHLCV, Chinese market OHLCV, standardized data with edge cases +- **Metadata**: Analysis metadata, batch analysis, error scenarios +- **Report Sections**: Complete report sections (market, sentiment, news, fundamentals, investment plans) +- **API Responses**: Mock OpenAI embeddings (single, batch, error responses) +- **Configurations**: Vendor-specific and LLM provider configurations + +**Quick Start**: +```python +from tests.fixtures import FixtureLoader + +# Load stock data +df = FixtureLoader.load_us_stock_data() + +# Load metadata +metadata = FixtureLoader.load_analysis_metadata("complete_analysis") + +# Load API responses +embedding = FixtureLoader.load_embedding_response() + +# Load configuration +config = FixtureLoader.load_default_config("complete_config") + +# Load with edge cases +empty_df = FixtureLoader.load_us_stock_data(edge_case="empty_data") +``` + +**Stock Data Fixtures**: +```python +# US market data (AAPL) +df = FixtureLoader.load_us_stock_data() + +# Chinese market data (standardized to English) +cn_df = FixtureLoader.load_cn_stock_data(standardize=True) + +# Standardized data (TSLA) +tsla_df = FixtureLoader.load_standardized_stock_data() + +# Edge cases for robustness testing +empty = FixtureLoader.load_us_stock_data(edge_case="empty_data") +single = FixtureLoader.load_us_stock_data(edge_case="single_row") +no_volume = FixtureLoader.load_us_stock_data(edge_case="missing_volume") +``` + +**Metadata Fixtures**: +```python +# Complete analysis +metadata = FixtureLoader.load_analysis_metadata("complete_analysis") +print(metadata["ticker"]) # 'AAPL' +print(metadata["status"]) # 'complete' + +# Partial analysis (missing sections) +partial = FixtureLoader.load_analysis_metadata("partial_analysis") + +# Error scenario +error = FixtureLoader.load_analysis_metadata("error_scenario") +``` + +**Report Section Fixtures**: +```python +# Load all report sections +sections = FixtureLoader.load_complete_report_sections() +print(sections.keys()) # All analyst reports + +# Load specific section +market = FixtureLoader.load_report_section("market_report") + +# Load partial sections (some None for testing) +partial = FixtureLoader.load_partial_report_sections() +``` + +**API Response Fixtures**: +```python +# Embedding response +response = FixtureLoader.load_embedding_response("single_text_embedding") +embedding_vector = response["data"][0]["embedding"] + +# Batch embeddings +batch = FixtureLoader.load_embedding_response("batch_text_embeddings") + +# Error responses +error = FixtureLoader.load_embedding_error("rate_limit_error") +``` + +**Configuration Fixtures**: +```python +# Complete configuration +config = FixtureLoader.load_default_config("complete_config") + +# Minimal configuration +minimal = FixtureLoader.load_default_config("minimal_config") + +# Vendor-specific +alpaca_config = FixtureLoader.load_vendor_config("alpaca") +akshare_config = FixtureLoader.load_vendor_config("akshare") + +# LLM provider configs +openrouter = FixtureLoader.load_llm_provider_config("openrouter") +anthropic = FixtureLoader.load_llm_provider_config("anthropic") +``` + +**Using Fixtures with Tests**: +```python +def test_stock_data_processing(): + """Test stock data processing with fixture.""" + # Load fixture + df = FixtureLoader.load_us_stock_data() + + # Act + result = process_stock_data(df) + + # Assert + assert len(result) == len(df) + assert result.index.equals(df.index) + +def test_handles_empty_data(): + """Test graceful handling of empty data.""" + empty_df = FixtureLoader.load_us_stock_data(edge_case="empty_data") + + result = process_stock_data(empty_df) + + assert result is None or result.empty + +@pytest.mark.parametrize("edge_case", [ + "empty_data", + "single_row", + "missing_volume", + "out_of_order_dates" +]) +def test_edge_cases(edge_case): + """Test handling of various edge cases.""" + df = FixtureLoader.load_us_stock_data(edge_case=edge_case) + + # Should not raise exception + result = process_stock_data(df) + assert result is not None or df.empty +``` + +**Fixture Directory Structure**: +See `tests/fixtures/README.md` for complete documentation including: +- Detailed fixture descriptions and available examples +- DataFrame and datetime handling +- Advanced usage patterns +- Maintenance and contribution guidelines + ### Understanding the conftest.py Hierarchy TradingAgents provides fixtures at three levels: diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 00000000..0edab3a6 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,595 @@ +# Test Fixtures + +Centralized test fixtures for TradingAgents test suite. This directory provides mock data for stock market OHLCV data, analysis metadata, report sections, API responses, and configurations. + +## Directory Structure + +``` +tests/fixtures/ +β”œβ”€β”€ __init__.py # FixtureLoader class and convenience functions +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ stock_data/ # Stock market OHLCV data +β”‚ β”œβ”€β”€ us_market_ohlcv.json +β”‚ β”œβ”€β”€ cn_market_ohlcv.json +β”‚ └── standardized_ohlcv.json +β”œβ”€β”€ metadata/ # Analysis metadata +β”‚ └── analysis_metadata.json +β”œβ”€β”€ report_sections/ # Complete report sections +β”‚ └── complete_reports.json +β”œβ”€β”€ api_responses/ # Mock API responses +β”‚ └── openai_embeddings.json +└── configurations/ # Configuration examples + └── default_config.json +``` + +## Quick Start + +### Basic Usage + +```python +from tests.fixtures import FixtureLoader + +# Load US stock data +df = FixtureLoader.load_us_stock_data() +print(df.head()) + +# Load Chinese stock data (with Chinese column names) +cn_df = FixtureLoader.load_cn_stock_data() + +# Load Chinese stock data (standardized to English) +cn_df_std = FixtureLoader.load_cn_stock_data(standardize=True) + +# Load analysis metadata +metadata = FixtureLoader.load_analysis_metadata("complete_analysis") + +# Load report sections +sections = FixtureLoader.load_complete_report_sections() + +# Load API responses +embedding = FixtureLoader.load_embedding_response() + +# Load configuration +config = FixtureLoader.load_default_config("complete_config") +``` + +### Convenience Functions + +```python +# Import convenience functions directly +from tests.fixtures import ( + load_us_stock_data, + load_cn_stock_data, + load_analysis_metadata, + load_complete_report_sections, + load_embedding_response, + load_default_config, +) + +# Use them directly +df = load_us_stock_data() +metadata = load_analysis_metadata() +``` + +## Fixture Details + +### Stock Data Fixtures + +#### US Market Data (`stock_data/us_market_ohlcv.json`) + +OHLCV data for AAPL (Apple Inc.) from 2024-11-01 to 2024-11-15. + +**Columns**: Date, Open, High, Low, Close, Volume + +**Usage**: +```python +# Load main data +df = FixtureLoader.load_us_stock_data() + +# Load edge cases +empty = FixtureLoader.load_us_stock_data(edge_case="empty_data") +single = FixtureLoader.load_us_stock_data(edge_case="single_row") +missing_vol = FixtureLoader.load_us_stock_data(edge_case="missing_volume") +out_of_order = FixtureLoader.load_us_stock_data(edge_case="out_of_order_dates") +``` + +**Edge Cases**: +- `empty_data`: Empty DataFrame for testing empty data handling +- `single_row`: Single row of data +- `missing_volume`: Data with Volume column missing +- `out_of_order_dates`: Dates not in chronological order + +#### Chinese Market Data (`stock_data/cn_market_ohlcv.json`) + +OHLCV data for 600519.SH (θ΄΅ε·žθŒ…ε° - Kweichow Moutai) from 2024-11-01 to 2024-11-15. + +**Columns (Chinese)**: ζ—₯期, εΌ€η›˜, ζœ€ι«˜, ζœ€δ½Ž, ζ”Άη›˜, ζˆδΊ€ι‡ + +**Column Mapping**: +- ζ—₯期 β†’ Date +- εΌ€η›˜ β†’ Open +- ζœ€ι«˜ β†’ High +- ζœ€δ½Ž β†’ Low +- ζ”Άη›˜ β†’ Close +- ζˆδΊ€ι‡ β†’ Volume + +**Usage**: +```python +# Load with Chinese column names +cn_df = FixtureLoader.load_cn_stock_data() +print(cn_df.columns) # ['εΌ€η›˜', 'ζœ€ι«˜', 'ζœ€δ½Ž', 'ζ”Άη›˜', 'ζˆδΊ€ι‡'] + +# Load with standardized English column names +cn_df_std = FixtureLoader.load_cn_stock_data(standardize=True) +print(cn_df_std.columns) # ['Open', 'High', 'Low', 'Close', 'Volume'] + +# Load edge cases +empty = FixtureLoader.load_cn_stock_data(edge_case="empty_data") +mixed = FixtureLoader.load_cn_stock_data(edge_case="mixed_columns") +``` + +**Edge Cases**: +- `empty_data`: Empty DataFrame +- `mixed_columns`: Mix of Chinese and English column names + +#### Standardized Data (`stock_data/standardized_ohlcv.json`) + +Standardized OHLCV data for TSLA from 2024-11-01 to 2024-11-14. Represents data after processing and standardization, ready for technical analysis. + +**Usage**: +```python +df = FixtureLoader.load_standardized_stock_data() +``` + +### Metadata Fixtures + +#### Analysis Metadata (`metadata/analysis_metadata.json`) + +Metadata for stock analysis reports including ticker, date range, analysts, data vendors, LLM providers, and execution details. + +**Available Examples**: +- `complete_analysis`: Full analysis with all sections completed +- `partial_analysis`: Partial analysis with some missing sections +- `multi_ticker_batch`: Batch analysis for multiple tickers +- `chinese_market_analysis`: Chinese market analysis with localization +- `error_scenario`: Failed analysis with error details + +**Usage**: +```python +# Complete analysis +metadata = FixtureLoader.load_analysis_metadata("complete_analysis") +print(metadata["ticker"]) # 'AAPL' +print(metadata["status"]) # 'complete' + +# Partial analysis +partial = FixtureLoader.load_analysis_metadata("partial_analysis") +print(partial["missing_sections"]) # ['news_report', 'fundamentals_report', ...] + +# Error scenario +error = FixtureLoader.load_analysis_metadata("error_scenario") +print(error["error_type"]) # 'DataNotFoundError' +``` + +### Report Section Fixtures + +#### Complete Reports (`report_sections/complete_reports.json`) + +Full report sections for comprehensive analysis. Includes all analyst reports from market analysis to final trade decision. + +**Available Sections**: +- `market_report`: Technical analysis and market overview +- `sentiment_report`: Social sentiment analysis +- `news_report`: News analysis and impact assessment +- `fundamentals_report`: Fundamental analysis with financials +- `investment_plan`: Long-term investment strategy +- `trader_investment_plan`: Short-term trading plan +- `final_trade_decision`: Executive decision and order details + +**Usage**: +```python +# Load all sections +sections = FixtureLoader.load_complete_report_sections() +print(sections["market_report"]["content"][:50]) + +# Load specific section +market = FixtureLoader.load_report_section("market_report") +print(market["analyst"]) # 'market' +print(market["generated_at"]) # datetime object + +# Load partial sections (some None) +partial = FixtureLoader.load_partial_report_sections() +print(partial["market_report"]) # Has content +print(partial["sentiment_report"]) # None +``` + +### API Response Fixtures + +#### OpenAI Embeddings (`api_responses/openai_embeddings.json`) + +Mock OpenAI API responses for embedding requests. Useful for testing without making actual API calls. + +**Available Examples**: +- `single_text_embedding`: Single embedding response +- `batch_text_embeddings`: Multiple embeddings in one response +- `financial_situation_embedding`: Embedding for financial situation text +- `large_embedding_1536`: Full-size 1536-dimension embedding (truncated in fixture) + +**Available Errors**: +- `rate_limit_error`: Rate limit exceeded error +- `invalid_api_key`: Invalid API key error +- `model_not_found`: Model not found error + +**Usage**: +```python +# Load embedding response +response = FixtureLoader.load_embedding_response("single_text_embedding") +embedding_vector = response["data"][0]["embedding"] +print(len(embedding_vector)) # 20 (truncated for testing) + +# Load batch embeddings +batch = FixtureLoader.load_embedding_response("batch_text_embeddings") +print(len(batch["data"])) # 3 + +# Load error response +error = FixtureLoader.load_embedding_error("rate_limit_error") +print(error["error"]["type"]) # 'rate_limit_error' +``` + +### Configuration Fixtures + +#### Default Configurations (`configurations/default_config.json`) + +Configuration examples for different scenarios and vendor setups. + +**Available Examples**: +- `complete_config`: Full configuration with all options +- `minimal_config`: Minimal required configuration +- `chinese_market_config`: Chinese market-specific configuration +- `high_frequency_config`: High-frequency trading configuration +- `testing_config`: Testing/development configuration + +**Vendor-Specific Configs**: +- `alpaca`: Alpaca API configuration +- `alpha_vantage`: Alpha Vantage API configuration +- `akshare`: AKShare (Chinese market) configuration +- `yfinance`: Yahoo Finance configuration + +**LLM Provider Configs**: +- `openrouter`: OpenRouter configuration +- `openai`: OpenAI configuration +- `anthropic`: Anthropic configuration +- `ollama`: Ollama (local) configuration + +**Usage**: +```python +# Load complete config +config = FixtureLoader.load_default_config("complete_config") +print(config["data_vendor"]) # 'alpaca' + +# Load minimal config +minimal = FixtureLoader.load_default_config("minimal_config") + +# Load vendor-specific config +alpaca_config = FixtureLoader.load_vendor_config("alpaca") +print(alpaca_config["paper_trading"]) # True + +# Load LLM provider config +openrouter = FixtureLoader.load_llm_provider_config("openrouter") +print(openrouter["backend_url"]) # 'https://openrouter.ai/api/v1' +``` + +## Advanced Usage + +### Loading Custom Fixtures + +```python +# Load arbitrary JSON fixture +custom_data = FixtureLoader.load_json_fixture("path/to/custom.json") + +# Load as DataFrame +df = FixtureLoader.load_dataframe_fixture( + "path/to/data.json", + data_key="data", + date_column="Date", + set_index=True +) +``` + +### Datetime Parsing + +All datetime strings in JSON fixtures are automatically parsed to Python `datetime` objects. Supports ISO 8601 format: + +```json +{ + "generated_at": "2024-12-26T14:30:00", + "analysis_date": "2024-12-26", + "created_at": "2024-12-26T10:30:00+08:00" +} +``` + +After loading: +```python +metadata = FixtureLoader.load_analysis_metadata() +print(type(metadata["generated_at"])) # +``` + +### Working with DataFrames + +```python +# Load stock data +df = FixtureLoader.load_us_stock_data() + +# Date is already the index +print(df.index.name) # 'Date' +print(df.index.dtype) # datetime64[ns] + +# Columns are OHLCV +print(df.columns.tolist()) # ['Open', 'High', 'Low', 'Close', 'Volume'] + +# Ready for technical analysis +df['SMA_20'] = df['Close'].rolling(window=20).mean() +``` + +### Testing Edge Cases + +```python +import pytest + +def test_handles_empty_data(): + """Test that function handles empty DataFrame gracefully.""" + empty_df = FixtureLoader.load_us_stock_data(edge_case="empty_data") + assert empty_df.empty + + result = process_stock_data(empty_df) + assert result is None or result.empty + +def test_handles_missing_volume(): + """Test that function handles missing Volume column.""" + df = FixtureLoader.load_us_stock_data(edge_case="missing_volume") + assert "Volume" not in df.columns + + # Should not raise error + result = calculate_indicators(df) + assert result is not None +``` + +## Writing Tests with Fixtures + +### Basic Test Example + +```python +import pytest +from tests.fixtures import FixtureLoader + +def test_stock_data_processing(): + """Test stock data processing with fixture.""" + # Arrange + df = FixtureLoader.load_us_stock_data() + + # Act + result = process_stock_data(df) + + # Assert + assert result is not None + assert len(result) == len(df) + assert result.index.equals(df.index) +``` + +### Pytest Fixture Integration + +```python +import pytest +from tests.fixtures import FixtureLoader + +@pytest.fixture +def us_stock_data(): + """Provide US stock data for tests.""" + return FixtureLoader.load_us_stock_data() + +@pytest.fixture +def analysis_metadata(): + """Provide analysis metadata for tests.""" + return FixtureLoader.load_analysis_metadata() + +def test_with_fixtures(us_stock_data, analysis_metadata): + """Test using pytest fixtures.""" + result = analyze_stock( + data=us_stock_data, + ticker=analysis_metadata["ticker"] + ) + assert result["ticker"] == "AAPL" +``` + +### Parameterized Tests + +```python +import pytest +from tests.fixtures import FixtureLoader + +@pytest.mark.parametrize("edge_case", [ + "empty_data", + "single_row", + "missing_volume", + "out_of_order_dates" +]) +def test_edge_cases(edge_case): + """Test handling of various edge cases.""" + df = FixtureLoader.load_us_stock_data(edge_case=edge_case) + + # Should not raise exception + result = process_stock_data(df) + + # Should handle gracefully + assert result is not None or df.empty +``` + +### Mocking API Responses + +```python +from unittest.mock import patch, MagicMock +from tests.fixtures import FixtureLoader + +def test_embedding_api_call(): + """Test embedding API call with mock response.""" + # Load mock response + mock_response = FixtureLoader.load_embedding_response() + + # Mock the API client + with patch('openai.OpenAI') as mock_client: + mock_client.return_value.embeddings.create.return_value = MagicMock( + **mock_response + ) + + # Test function that uses embeddings + result = get_text_embedding("test text") + + # Verify + assert result is not None + assert len(result) > 0 +``` + +## Best Practices + +### 1. Use Fixtures Over Hardcoded Data + +**Bad**: +```python +def test_stock_processing(): + df = pd.DataFrame({ + 'Date': ['2024-11-01', '2024-11-02'], + 'Close': [100, 101] + }) + # ... +``` + +**Good**: +```python +def test_stock_processing(): + df = FixtureLoader.load_us_stock_data() + # ... +``` + +### 2. Test Edge Cases + +Always test edge cases using the provided edge case fixtures: + +```python +def test_empty_data_handling(): + df = FixtureLoader.load_us_stock_data(edge_case="empty_data") + result = process_data(df) + assert result is not None # Should not crash + +def test_missing_column_handling(): + df = FixtureLoader.load_us_stock_data(edge_case="missing_volume") + result = calculate_volume_indicators(df) + assert result is None or result.empty # Graceful degradation +``` + +### 3. Use Appropriate Fixture Type + +- **Unit tests**: Use small, focused fixtures (single row, minimal data) +- **Integration tests**: Use complete fixtures (full data range) +- **Performance tests**: Use edge cases (empty data, large datasets) + +### 4. Document Custom Fixtures + +If you add custom fixtures: + +1. Add JSON file to appropriate subdirectory +2. Add loader method to `FixtureLoader` class +3. Add documentation to this README +4. Add usage examples + +### 5. Keep Fixtures Realistic + +Fixtures should represent realistic data: +- Use actual stock symbols (AAPL, TSLA, 600519.SH) +- Use realistic price ranges and volumes +- Include realistic metadata and timestamps +- Mirror actual API response structures + +## Maintenance + +### Adding New Fixtures + +1. **Create JSON file** in appropriate subdirectory +2. **Follow naming convention**: `{category}_{description}.json` +3. **Include description** field in JSON +4. **Add loader method** to `FixtureLoader` class +5. **Update this README** with usage examples +6. **Add tests** for the new fixture loader + +### Modifying Existing Fixtures + +1. **Maintain backward compatibility** when possible +2. **Update documentation** if structure changes +3. **Update affected tests** +4. **Consider adding new example** instead of modifying existing + +### Versioning + +Fixtures are versioned with the project. Breaking changes to fixture structure should: + +1. Increment project version +2. Document migration path in CHANGELOG +3. Provide legacy fixtures for compatibility (if needed) + +## Troubleshooting + +### FileNotFoundError + +```python +# Error: FileNotFoundError: Fixture not found +# Solution: Check path is relative to fixtures directory +df = FixtureLoader.load_json_fixture("stock_data/us_market_ohlcv.json") # Correct +``` + +### KeyError: 'data' + +```python +# Error: KeyError: Key 'data' not found in fixture +# Solution: Specify correct data_key +df = FixtureLoader.load_dataframe_fixture( + "path/to/file.json", + data_key="examples" # Not "data" +) +``` + +### Datetime Not Parsed + +```python +# Dates are strings instead of datetime objects +# Solution: Ensure date format is ISO 8601 +# Good: "2024-11-01T00:00:00" or "2024-11-01" +# Bad: "11/01/2024" or "Nov 1, 2024" +``` + +### Empty DataFrame + +```python +# Getting empty DataFrame unexpectedly +# Check if loading edge case by mistake +df = FixtureLoader.load_us_stock_data() # Main data +df = FixtureLoader.load_us_stock_data(edge_case=None) # Explicit +``` + +## Contributing + +When contributing new fixtures: + +1. Follow existing JSON structure patterns +2. Use UTF-8 encoding (especially for Chinese characters) +3. Format dates as ISO 8601 strings +4. Include both main data and edge cases +5. Add comprehensive docstrings to loader methods +6. Update this README with usage examples +7. Add unit tests for new loader methods + +## License + +These fixtures are part of the TradingAgents project and are provided for testing purposes only. Stock data is synthetic and should not be used for actual trading decisions. + +## See Also + +- [Testing Guide](../README.md) - Testing best practices +- [Pytest Documentation](https://docs.pytest.org/) - Pytest framework +- [Pandas Documentation](https://pandas.pydata.org/) - DataFrame operations diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 00000000..f2f52e41 --- /dev/null +++ b/tests/fixtures/__init__.py @@ -0,0 +1,592 @@ +""" +Test fixtures module providing mock data for TradingAgents tests. + +This module provides a centralized FixtureLoader class for loading JSON-based +test fixtures including stock data, metadata, report sections, API responses, +and configurations. All datetime values are automatically parsed from ISO 8601 +format strings. + +Features: +- JSON file loading with automatic datetime parsing +- DataFrame conversion for stock data +- Specialized loaders for different fixture types +- UTF-8 encoding support for Chinese market data +- Edge case handling for robust testing + +Usage: + from tests.fixtures import FixtureLoader + + # Load stock data as DataFrame + us_data = FixtureLoader.load_us_stock_data() + cn_data = FixtureLoader.load_cn_stock_data() + + # Load metadata + metadata = FixtureLoader.load_analysis_metadata("complete_analysis") + + # Load report sections + reports = FixtureLoader.load_complete_report_sections() + + # Load API responses + embedding = FixtureLoader.load_embedding_response() + + # Load configuration + config = FixtureLoader.load_default_config("complete_config") + +Directory Structure: + tests/fixtures/ + β”œβ”€β”€ __init__.py (this file) + β”œβ”€β”€ stock_data/ + β”‚ β”œβ”€β”€ us_market_ohlcv.json + β”‚ β”œβ”€β”€ cn_market_ohlcv.json + β”‚ └── standardized_ohlcv.json + β”œβ”€β”€ metadata/ + β”‚ └── analysis_metadata.json + β”œβ”€β”€ report_sections/ + β”‚ └── complete_reports.json + β”œβ”€β”€ api_responses/ + β”‚ └── openai_embeddings.json + └── configurations/ + └── default_config.json +""" + +import json +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +import pandas as pd + + +class FixtureLoader: + """ + Centralized fixture loader for test data. + + Provides static methods for loading various types of test fixtures + with automatic datetime parsing and DataFrame conversion where appropriate. + """ + + FIXTURES_DIR = Path(__file__).parent + + @classmethod + def load_json_fixture(cls, relative_path: str) -> Dict[str, Any]: + """ + Load a JSON fixture file with automatic datetime parsing. + + Converts ISO 8601 datetime strings to Python datetime objects. + Supports nested dictionaries and lists. + + Args: + relative_path: Path relative to fixtures directory (e.g., "stock_data/us_market_ohlcv.json") + + Returns: + Dictionary containing the parsed JSON data with datetime objects + + Raises: + FileNotFoundError: If fixture file doesn't exist + json.JSONDecodeError: If file contains invalid JSON + + Example: + >>> data = FixtureLoader.load_json_fixture("stock_data/us_market_ohlcv.json") + >>> print(data["ticker"]) + 'AAPL' + """ + fixture_path = cls.FIXTURES_DIR / relative_path + + if not fixture_path.exists(): + raise FileNotFoundError(f"Fixture not found: {fixture_path}") + + with open(fixture_path, "r", encoding="utf-8") as f: + data = json.load(f) + + # Parse datetime strings recursively + return cls._parse_datetimes(data) + + @classmethod + def _parse_datetimes(cls, obj: Any) -> Any: + """ + Recursively parse ISO 8601 datetime strings to datetime objects. + + Handles dictionaries, lists, and nested structures. Attempts to parse + strings that look like ISO 8601 dates (contain 'T' and ':'). + + Args: + obj: Object to parse (dict, list, str, or other) + + Returns: + Object with datetime strings converted to datetime objects + """ + if isinstance(obj, dict): + return {key: cls._parse_datetimes(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [cls._parse_datetimes(item) for item in obj] + elif isinstance(obj, str): + # Try to parse as datetime if it looks like ISO 8601 + if "T" in obj or (obj.count("-") >= 2 and obj.count(":") >= 2): + try: + return datetime.fromisoformat(obj.replace("Z", "+00:00")) + except (ValueError, AttributeError): + return obj + return obj + else: + return obj + + @classmethod + def load_dataframe_fixture( + cls, + relative_path: str, + data_key: str = "data", + date_column: Optional[str] = "Date", + set_index: bool = True, + ) -> pd.DataFrame: + """ + Load a JSON fixture and convert to pandas DataFrame. + + Automatically parses datetime columns and optionally sets date as index. + Useful for stock OHLCV data and other time-series data. + + Args: + relative_path: Path to JSON fixture file + data_key: Key in JSON containing the data array (default: "data") + date_column: Name of date column to parse (default: "Date") + set_index: Whether to set date_column as index (default: True) + + Returns: + pandas DataFrame with parsed dates and optional date index + + Example: + >>> df = FixtureLoader.load_dataframe_fixture( + ... "stock_data/us_market_ohlcv.json", + ... data_key="data", + ... date_column="Date" + ... ) + >>> print(df.head()) + """ + fixture_data = cls.load_json_fixture(relative_path) + + # Extract data array + if data_key not in fixture_data: + raise KeyError(f"Key '{data_key}' not found in fixture {relative_path}") + + data = fixture_data[data_key] + + # Handle empty data edge case + if not data: + return pd.DataFrame() + + # Convert to DataFrame + df = pd.DataFrame(data) + + # Parse date column if specified + if date_column and date_column in df.columns: + df[date_column] = pd.to_datetime(df[date_column]) + + # Set as index if requested + if set_index: + df = df.set_index(date_column) + + return df + + # Stock Data Loaders + + @classmethod + def load_us_stock_data( + cls, edge_case: Optional[str] = None + ) -> pd.DataFrame: + """ + Load US market stock OHLCV data (AAPL). + + Args: + edge_case: Optional edge case to load instead of main data. + Options: "empty_data", "single_row", "missing_volume", + "out_of_order_dates" + + Returns: + DataFrame with OHLCV data, Date as index + + Example: + >>> df = FixtureLoader.load_us_stock_data() + >>> print(df.columns.tolist()) + ['Open', 'High', 'Low', 'Close', 'Volume'] + """ + fixture_data = cls.load_json_fixture("stock_data/us_market_ohlcv.json") + + # Select data source + if edge_case: + if "edge_cases" not in fixture_data or edge_case not in fixture_data["edge_cases"]: + raise ValueError(f"Edge case '{edge_case}' not found in US stock data fixture") + data = fixture_data["edge_cases"][edge_case] + else: + data = fixture_data["data"] + + # Handle empty data + if not data: + return pd.DataFrame() + + # Convert to DataFrame + df = pd.DataFrame(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"]) + df = df.set_index("Date") + + return df + + @classmethod + def load_cn_stock_data( + cls, edge_case: Optional[str] = None, standardize: bool = False + ) -> pd.DataFrame: + """ + Load Chinese market stock OHLCV data (600519.SH - Kweichow Moutai). + + Chinese market data uses localized column names (ζ—₯期, εΌ€η›˜, ζœ€ι«˜, ζœ€δ½Ž, ζ”Άη›˜, ζˆδΊ€ι‡). + Can optionally standardize to English column names. + + Args: + edge_case: Optional edge case to load instead of main data. + Options: "empty_data", "mixed_columns" + standardize: If True, convert Chinese column names to English + + Returns: + DataFrame with OHLCV data, date column as index + + Example: + >>> df = FixtureLoader.load_cn_stock_data() + >>> print(df.columns.tolist()) + ['εΌ€η›˜', 'ζœ€ι«˜', 'ζœ€δ½Ž', 'ζ”Άη›˜', 'ζˆδΊ€ι‡'] + + >>> df = FixtureLoader.load_cn_stock_data(standardize=True) + >>> print(df.columns.tolist()) + ['Open', 'High', 'Low', 'Close', 'Volume'] + """ + fixture_data = cls.load_json_fixture("stock_data/cn_market_ohlcv.json") + + # Select data source + if edge_case: + if "edge_cases" not in fixture_data or edge_case not in fixture_data["edge_cases"]: + raise ValueError(f"Edge case '{edge_case}' not found in CN stock data fixture") + data = fixture_data["edge_cases"][edge_case] + else: + data = fixture_data["data"] + + # Handle empty data + if not data: + return pd.DataFrame() + + # Convert to DataFrame + df = pd.DataFrame(data) + + # Standardize column names if requested + if standardize and "column_mapping" in fixture_data: + column_mapping = fixture_data["column_mapping"] + df = df.rename(columns=column_mapping) + + # Set date column as index + date_col = "Date" if standardize else "ζ—₯期" + if date_col in df.columns: + df[date_col] = pd.to_datetime(df[date_col]) + df = df.set_index(date_col) + + return df + + @classmethod + def load_standardized_stock_data(cls) -> pd.DataFrame: + """ + Load standardized OHLCV data (TSLA) ready for technical analysis. + + This fixture represents data after standardization - all English column + names, Date as index, ready for technical indicator calculation. + + Returns: + DataFrame with standardized OHLCV data + + Example: + >>> df = FixtureLoader.load_standardized_stock_data() + >>> print(df.index.name) + 'Date' + """ + return cls.load_dataframe_fixture( + "stock_data/standardized_ohlcv.json", + data_key="data", + date_column="Date", + set_index=True, + ) + + # Metadata Loaders + + @classmethod + def load_analysis_metadata(cls, example_name: str = "complete_analysis") -> Dict[str, Any]: + """ + Load analysis metadata fixture. + + Provides metadata for stock analysis reports including ticker, date range, + analysts, vendors, LLM providers, and execution details. + + Args: + example_name: Name of the example to load. + Options: "complete_analysis", "partial_analysis", + "multi_ticker_batch", "chinese_market_analysis", + "error_scenario" + + Returns: + Dictionary containing analysis metadata with parsed datetimes + + Example: + >>> metadata = FixtureLoader.load_analysis_metadata("complete_analysis") + >>> print(metadata["ticker"]) + 'AAPL' + >>> print(metadata["status"]) + 'complete' + """ + fixture_data = cls.load_json_fixture("metadata/analysis_metadata.json") + + if "examples" not in fixture_data or example_name not in fixture_data["examples"]: + raise ValueError(f"Example '{example_name}' not found in analysis metadata fixture") + + return fixture_data["examples"][example_name] + + # Report Section Loaders + + @classmethod + def load_complete_report_sections(cls) -> Dict[str, Dict[str, Any]]: + """ + Load complete report sections for comprehensive analysis. + + Returns all sections: market_report, sentiment_report, news_report, + fundamentals_report, investment_plan, trader_investment_plan, + final_trade_decision. + + Returns: + Dictionary mapping section names to section data (with content) + + Example: + >>> sections = FixtureLoader.load_complete_report_sections() + >>> print(sections["market_report"]["content"][:50]) + '# Market Analysis for AAPL...' + """ + fixture_data = cls.load_json_fixture("report_sections/complete_reports.json") + return fixture_data["sections"] + + @classmethod + def load_partial_report_sections(cls) -> Dict[str, Optional[str]]: + """ + Load partial report sections (some analysts haven't completed). + + Useful for testing scenarios where only some sections are available. + + Returns: + Dictionary mapping section names to content (None for incomplete sections) + + Example: + >>> sections = FixtureLoader.load_partial_report_sections() + >>> print(sections["market_report"]) # Has content + >>> print(sections["sentiment_report"]) # None + """ + fixture_data = cls.load_json_fixture("report_sections/complete_reports.json") + return fixture_data["partial_sections"] + + @classmethod + def load_report_section(cls, section_name: str) -> Dict[str, Any]: + """ + Load a specific report section. + + Args: + section_name: Name of section to load. Options: "market_report", + "sentiment_report", "news_report", "fundamentals_report", + "investment_plan", "trader_investment_plan", + "final_trade_decision" + + Returns: + Dictionary containing section metadata and content + + Example: + >>> section = FixtureLoader.load_report_section("market_report") + >>> print(section["analyst"]) + 'market' + """ + sections = cls.load_complete_report_sections() + + if section_name not in sections: + raise ValueError(f"Section '{section_name}' not found in complete reports fixture") + + return sections[section_name] + + # API Response Loaders + + @classmethod + def load_embedding_response( + cls, example_name: str = "single_text_embedding" + ) -> Dict[str, Any]: + """ + Load OpenAI API embedding response fixture. + + Provides mock API responses for embedding requests, useful for testing + without making actual API calls. + + Args: + example_name: Name of the example to load. + Options: "single_text_embedding", "batch_text_embeddings", + "financial_situation_embedding", "large_embedding_1536" + + Returns: + Dictionary containing mock OpenAI embedding API response + + Example: + >>> response = FixtureLoader.load_embedding_response() + >>> print(response["data"][0]["embedding"][:3]) + [-0.006929283495992422, -0.005336422007530928, 0.00047350498218461871] + """ + fixture_data = cls.load_json_fixture("api_responses/openai_embeddings.json") + + if "examples" not in fixture_data or example_name not in fixture_data["examples"]: + raise ValueError(f"Example '{example_name}' not found in embeddings fixture") + + return fixture_data["examples"][example_name] + + @classmethod + def load_embedding_error(cls, error_type: str = "rate_limit_error") -> Dict[str, Any]: + """ + Load OpenAI API error response fixture. + + Useful for testing error handling and retry logic. + + Args: + error_type: Type of error to load. + Options: "rate_limit_error", "invalid_api_key", "model_not_found" + + Returns: + Dictionary containing mock OpenAI error response + + Example: + >>> error = FixtureLoader.load_embedding_error("rate_limit_error") + >>> print(error["error"]["type"]) + 'rate_limit_error' + """ + fixture_data = cls.load_json_fixture("api_responses/openai_embeddings.json") + + if "error_responses" not in fixture_data or error_type not in fixture_data["error_responses"]: + raise ValueError(f"Error type '{error_type}' not found in embeddings fixture") + + return fixture_data["error_responses"][error_type] + + # Configuration Loaders + + @classmethod + def load_default_config(cls, example_name: str = "complete_config") -> Dict[str, Any]: + """ + Load configuration fixture. + + Provides default and specialized configurations for testing different + scenarios and vendor setups. + + Args: + example_name: Name of the configuration example to load. + Options: "complete_config", "minimal_config", + "chinese_market_config", "high_frequency_config", + "testing_config" + + Returns: + Dictionary containing configuration settings + + Example: + >>> config = FixtureLoader.load_default_config("complete_config") + >>> print(config["data_vendor"]) + 'alpaca' + >>> print(config["llm_provider"]) + 'openrouter' + """ + fixture_data = cls.load_json_fixture("configurations/default_config.json") + + if "examples" not in fixture_data or example_name not in fixture_data["examples"]: + raise ValueError(f"Example '{example_name}' not found in config fixture") + + return fixture_data["examples"][example_name] + + @classmethod + def load_vendor_config(cls, vendor_name: str) -> Dict[str, Any]: + """ + Load vendor-specific configuration. + + Args: + vendor_name: Name of the vendor. + Options: "alpaca", "alpha_vantage", "akshare", "yfinance" + + Returns: + Dictionary containing vendor-specific configuration + + Example: + >>> config = FixtureLoader.load_vendor_config("alpaca") + >>> print(config["paper_trading"]) + True + """ + fixture_data = cls.load_json_fixture("configurations/default_config.json") + + if "vendor_specific_configs" not in fixture_data or vendor_name not in fixture_data["vendor_specific_configs"]: + raise ValueError(f"Vendor config '{vendor_name}' not found in config fixture") + + return fixture_data["vendor_specific_configs"][vendor_name] + + @classmethod + def load_llm_provider_config(cls, provider_name: str) -> Dict[str, Any]: + """ + Load LLM provider-specific configuration. + + Args: + provider_name: Name of the LLM provider. + Options: "openrouter", "openai", "anthropic", "ollama" + + Returns: + Dictionary containing LLM provider-specific configuration + + Example: + >>> config = FixtureLoader.load_llm_provider_config("openrouter") + >>> print(config["backend_url"]) + 'https://openrouter.ai/api/v1' + """ + fixture_data = cls.load_json_fixture("configurations/default_config.json") + + if "llm_provider_configs" not in fixture_data or provider_name not in fixture_data["llm_provider_configs"]: + raise ValueError(f"LLM provider config '{provider_name}' not found in config fixture") + + return fixture_data["llm_provider_configs"][provider_name] + + +# Convenience functions for common use cases + +def load_us_stock_data(**kwargs) -> pd.DataFrame: + """Convenience function for loading US stock data.""" + return FixtureLoader.load_us_stock_data(**kwargs) + + +def load_cn_stock_data(**kwargs) -> pd.DataFrame: + """Convenience function for loading Chinese stock data.""" + return FixtureLoader.load_cn_stock_data(**kwargs) + + +def load_analysis_metadata(example_name: str = "complete_analysis") -> Dict[str, Any]: + """Convenience function for loading analysis metadata.""" + return FixtureLoader.load_analysis_metadata(example_name) + + +def load_complete_report_sections() -> Dict[str, Dict[str, Any]]: + """Convenience function for loading complete report sections.""" + return FixtureLoader.load_complete_report_sections() + + +def load_embedding_response(example_name: str = "single_text_embedding") -> Dict[str, Any]: + """Convenience function for loading embedding API responses.""" + return FixtureLoader.load_embedding_response(example_name) + + +def load_default_config(example_name: str = "complete_config") -> Dict[str, Any]: + """Convenience function for loading configuration.""" + return FixtureLoader.load_default_config(example_name) + + +__all__ = [ + "FixtureLoader", + "load_us_stock_data", + "load_cn_stock_data", + "load_analysis_metadata", + "load_complete_report_sections", + "load_embedding_response", + "load_default_config", +] diff --git a/tests/fixtures/api_responses/openai_embeddings.json b/tests/fixtures/api_responses/openai_embeddings.json new file mode 100644 index 00000000..e1dfb040 --- /dev/null +++ b/tests/fixtures/api_responses/openai_embeddings.json @@ -0,0 +1,199 @@ +{ + "description": "Mock OpenAI API embedding responses for testing", + "model": "text-embedding-3-small", + "examples": { + "single_text_embedding": { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + -0.006929283495992422, + -0.005336422007530928, + 0.00047350498218461871, + -0.024047505110502243, + 0.013851520791649818, + -0.02005230076611042, + 0.0052589345723390579, + -0.011878303624689579, + -0.00025520036462694407, + 0.015827439725399017, + -0.010150175541639328, + 0.023847095295786858, + -0.0088148806244134903, + 0.019137535244226456, + -0.003254246478900313, + 0.007801613025367260, + -0.012429786287248135, + 0.009863543696701527, + -0.002845674939453602, + 0.004567321203649044 + ] + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + }, + "batch_text_embeddings": { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + -0.006929283495992422, + -0.005336422007530928, + 0.00047350498218461871, + -0.024047505110502243, + 0.013851520791649818, + -0.02005230076611042, + 0.0052589345723390579, + -0.011878303624689579, + -0.00025520036462694407, + 0.015827439725399017 + ] + }, + { + "object": "embedding", + "index": 1, + "embedding": [ + 0.012345678901234567, + -0.009876543210987654, + 0.005432109876543211, + -0.018765432109876543, + 0.007654321098765432, + -0.015432109876543211, + 0.009123456789012345, + -0.013456789012345678, + 0.002345678901234567, + 0.011234567890123456 + ] + }, + { + "object": "embedding", + "index": 2, + "embedding": [ + -0.008765432109876543, + 0.013456789012345678, + -0.003456789012345678, + 0.009876543210987654, + -0.007654321098765432, + 0.015432109876543211, + -0.011234567890123456, + 0.006789012345678901, + -0.002345678901234567, + 0.010123456789012345 + ] + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 24, + "total_tokens": 24 + } + }, + "financial_situation_embedding": { + "description": "Embedding for a financial situation used in memory system", + "input": "Market showing strong bullish momentum with RSI at 68.5 and price breaking above resistance at $240", + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.023847095295786858, + -0.0088148806244134903, + 0.019137535244226456, + -0.003254246478900313, + 0.007801613025367260, + -0.012429786287248135, + 0.009863543696701527, + -0.002845674939453602, + 0.004567321203649044, + -0.006929283495992422, + -0.005336422007530928, + 0.00047350498218461871, + -0.024047505110502243, + 0.013851520791649818, + -0.02005230076611042, + 0.0052589345723390579, + -0.011878303624689579, + -0.00025520036462694407, + 0.015827439725399017, + -0.010150175541639328 + ] + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 17, + "total_tokens": 17 + } + }, + "large_embedding_1536": { + "description": "Full-size embedding (1536 dimensions) - truncated for brevity in fixture", + "note": "In real usage, this would have 1536 float values. For testing, use smaller dimension or generate random values.", + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + -0.006929283495992422, + -0.005336422007530928, + 0.00047350498218461871 + ], + "_note": "... (1533 more values truncated for readability)" + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + }, + "error_responses": { + "rate_limit_error": { + "error": { + "message": "Rate limit exceeded. Please try again later.", + "type": "rate_limit_error", + "param": null, + "code": "rate_limit_exceeded" + } + }, + "invalid_api_key": { + "error": { + "message": "Incorrect API key provided. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } + }, + "model_not_found": { + "error": { + "message": "The model `invalid-model` does not exist", + "type": "invalid_request_error", + "param": null, + "code": "model_not_found" + } + } + }, + "usage_notes": { + "embedding_dimensions": { + "text-embedding-3-small": 1536, + "text-embedding-3-large": 3072, + "text-embedding-ada-002": 1536 + }, + "testing_recommendations": [ + "For unit tests, use small embeddings (10-20 dimensions) to save memory", + "For integration tests, use realistic dimension counts but mock the API", + "Use consistent random seeds for reproducible test embeddings", + "Test edge cases: empty text, very long text, special characters" + ] + } +} diff --git a/tests/fixtures/configurations/default_config.json b/tests/fixtures/configurations/default_config.json new file mode 100644 index 00000000..aa67aba1 --- /dev/null +++ b/tests/fixtures/configurations/default_config.json @@ -0,0 +1,200 @@ +{ + "description": "Default configuration fixtures for testing", + "examples": { + "complete_config": { + "data_vendor": "alpaca", + "llm_provider": "openrouter", + "embedding_backend": "openai", + "embedding_model": "text-embedding-3-small", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "market": "US", + "analysis_period_days": 30, + "output_dir": "./reports", + "log_level": "INFO", + "retry_attempts": 3, + "timeout_seconds": 30, + "rate_limit": { + "requests_per_minute": 60, + "requests_per_day": 10000 + }, + "api_keys": { + "openrouter_api_key": "${OPENROUTER_API_KEY}", + "openai_api_key": "${OPENAI_API_KEY}", + "alpaca_api_key": "${ALPACA_API_KEY}", + "alpaca_secret_key": "${ALPACA_SECRET_KEY}", + "alpha_vantage_api_key": "${ALPHA_VANTAGE_API_KEY}" + } + }, + "minimal_config": { + "data_vendor": "yfinance", + "llm_provider": "openai", + "shallow_thinker": "gpt-4-turbo", + "deep_thinker": "gpt-4" + }, + "chinese_market_config": { + "data_vendor": "akshare", + "llm_provider": "openrouter", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "market": "CN", + "language": "zh-CN", + "output_dir": "./reports/cn", + "analysis_period_days": 30 + }, + "high_frequency_config": { + "data_vendor": "alpaca", + "llm_provider": "openai", + "shallow_thinker": "gpt-4-turbo", + "deep_thinker": "gpt-4-turbo", + "market": "US", + "analysis_period_days": 7, + "update_frequency": "hourly", + "retry_attempts": 5, + "timeout_seconds": 60, + "rate_limit": { + "requests_per_minute": 120, + "requests_per_day": 50000 + } + }, + "testing_config": { + "data_vendor": "mock", + "llm_provider": "mock", + "shallow_thinker": "mock-model", + "deep_thinker": "mock-model", + "market": "US", + "analysis_period_days": 10, + "output_dir": "/tmp/test_reports", + "log_level": "DEBUG", + "retry_attempts": 1, + "timeout_seconds": 5, + "use_cache": false, + "mock_mode": true + } + }, + "vendor_specific_configs": { + "alpaca": { + "data_vendor": "alpaca", + "alpaca_base_url": "https://paper-api.alpaca.markets", + "alpaca_data_url": "https://data.alpaca.markets", + "paper_trading": true, + "market_data_feed": "iex" + }, + "alpha_vantage": { + "data_vendor": "alpha_vantage", + "alpha_vantage_premium": false, + "requests_per_minute": 5, + "requests_per_day": 500, + "output_size": "compact" + }, + "akshare": { + "data_vendor": "akshare", + "market": "CN", + "adjust": "qfq", + "timeout_seconds": 60, + "retry_attempts": 5 + }, + "yfinance": { + "data_vendor": "yfinance", + "interval": "1d", + "auto_adjust": true, + "prepost": false, + "threads": true + } + }, + "llm_provider_configs": { + "openrouter": { + "llm_provider": "openrouter", + "backend_url": "https://openrouter.ai/api/v1", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "temperature": 0.7, + "max_tokens": 4096, + "headers": { + "HTTP-Referer": "https://github.com/TauricResearch/TradingAgents", + "X-Title": "TradingAgents" + } + }, + "openai": { + "llm_provider": "openai", + "backend_url": "https://api.openai.com/v1", + "shallow_thinker": "gpt-4-turbo", + "deep_thinker": "gpt-4", + "temperature": 0.7, + "max_tokens": 4096 + }, + "anthropic": { + "llm_provider": "anthropic", + "backend_url": "https://api.anthropic.com/v1", + "shallow_thinker": "claude-3-sonnet-20240229", + "deep_thinker": "claude-3-opus-20240229", + "temperature": 0.7, + "max_tokens": 4096 + }, + "ollama": { + "llm_provider": "ollama", + "backend_url": "http://localhost:11434/v1", + "shallow_thinker": "llama2", + "deep_thinker": "mixtral", + "temperature": 0.7 + } + }, + "environment_variables": { + "development": { + "LOG_LEVEL": "DEBUG", + "USE_CACHE": "true", + "MOCK_MODE": "false", + "OUTPUT_DIR": "./reports/dev" + }, + "testing": { + "LOG_LEVEL": "DEBUG", + "USE_CACHE": "false", + "MOCK_MODE": "true", + "OUTPUT_DIR": "/tmp/test_reports" + }, + "production": { + "LOG_LEVEL": "INFO", + "USE_CACHE": "true", + "MOCK_MODE": "false", + "OUTPUT_DIR": "./reports/prod" + } + }, + "schema": { + "required_fields": [ + "data_vendor", + "llm_provider" + ], + "optional_fields": [ + "shallow_thinker", + "deep_thinker", + "market", + "analysis_period_days", + "output_dir", + "log_level", + "retry_attempts", + "timeout_seconds", + "rate_limit", + "api_keys" + ], + "data_vendor_options": [ + "alpaca", + "alpha_vantage", + "akshare", + "yfinance", + "mock" + ], + "llm_provider_options": [ + "openrouter", + "openai", + "anthropic", + "ollama", + "mock" + ], + "market_options": [ + "US", + "CN", + "EU", + "JP" + ] + } +} diff --git a/tests/fixtures/metadata/analysis_metadata.json b/tests/fixtures/metadata/analysis_metadata.json new file mode 100644 index 00000000..2b9eb7d5 --- /dev/null +++ b/tests/fixtures/metadata/analysis_metadata.json @@ -0,0 +1,99 @@ +{ + "description": "Analysis metadata for stock reports", + "examples": { + "complete_analysis": { + "ticker": "AAPL", + "analysis_date": "2024-12-26", + "date_range": "2024-11-26 to 2024-12-26", + "analysts": ["market", "sentiment", "news", "fundamentals"], + "data_vendor": "alpaca", + "llm_provider": "openrouter", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "generated_at": "2024-12-26T14:30:00", + "execution_time_seconds": 45.2, + "sections_completed": 7, + "status": "complete" + }, + "partial_analysis": { + "ticker": "TSLA", + "analysis_date": "2024-12-26", + "date_range": "2024-11-26 to 2024-12-26", + "analysts": ["market", "sentiment"], + "data_vendor": "yfinance", + "llm_provider": "openai", + "shallow_thinker": "gpt-4-turbo", + "deep_thinker": "gpt-4", + "generated_at": "2024-12-26T15:45:00", + "execution_time_seconds": 23.7, + "sections_completed": 2, + "status": "partial", + "missing_sections": ["news_report", "fundamentals_report", "investment_plan"] + }, + "multi_ticker_batch": { + "tickers": ["AAPL", "MSFT", "GOOGL", "AMZN"], + "analysis_date": "2024-12-26", + "date_range": "2024-11-26 to 2024-12-26", + "batch_id": "batch_20241226_001", + "data_vendor": "alpha_vantage", + "llm_provider": "openrouter", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "generated_at": "2024-12-26T16:00:00", + "total_execution_time_seconds": 312.5, + "completed_tickers": 4, + "failed_tickers": 0, + "status": "complete" + }, + "chinese_market_analysis": { + "ticker": "600519.SH", + "ticker_name": "θ΄΅ε·žθŒ…ε°", + "analysis_date": "2024-12-26", + "date_range": "2024-11-26 to 2024-12-26", + "market": "CN", + "analysts": ["market", "fundamentals"], + "data_vendor": "akshare", + "llm_provider": "openrouter", + "shallow_thinker": "anthropic/claude-3.5-sonnet", + "deep_thinker": "anthropic/claude-opus-4.5", + "generated_at": "2024-12-26T10:30:00+08:00", + "execution_time_seconds": 38.9, + "language": "zh-CN", + "status": "complete" + }, + "error_scenario": { + "ticker": "INVALID", + "analysis_date": "2024-12-26", + "data_vendor": "yfinance", + "llm_provider": "openai", + "generated_at": "2024-12-26T17:00:00", + "status": "failed", + "error_type": "DataNotFoundError", + "error_message": "No data found for ticker INVALID", + "retry_count": 3, + "last_retry_at": "2024-12-26T17:02:30" + } + }, + "schema": { + "required_fields": [ + "ticker", + "analysis_date", + "data_vendor", + "llm_provider", + "generated_at", + "status" + ], + "optional_fields": [ + "date_range", + "analysts", + "shallow_thinker", + "deep_thinker", + "execution_time_seconds", + "sections_completed", + "missing_sections", + "error_type", + "error_message" + ], + "status_values": ["complete", "partial", "failed", "in_progress"] + } +} diff --git a/tests/fixtures/report_sections/complete_reports.json b/tests/fixtures/report_sections/complete_reports.json new file mode 100644 index 00000000..bafaa3f0 --- /dev/null +++ b/tests/fixtures/report_sections/complete_reports.json @@ -0,0 +1,59 @@ +{ + "description": "Complete report sections for comprehensive analysis", + "ticker": "AAPL", + "analysis_date": "2024-12-26", + "sections": { + "market_report": { + "section_name": "market_report", + "analyst": "market", + "generated_at": "2024-12-26T14:25:00", + "content": "# Market Analysis for AAPL\n\n## Executive Summary\n\nAPPL demonstrates strong bullish momentum with consistent upward trajectory over the past 30 days. Price has advanced from $225.50 to $239.25, representing a 6.1% gain.\n\n## Technical Analysis\n\n### Price Action\n- **Current Price**: $239.25\n- **30-Day Range**: $224.10 - $240.10\n- **Trend**: Strong uptrend with higher highs and higher lows\n- **Support Levels**: $235.00, $230.00, $225.00\n- **Resistance Levels**: $240.00, $245.00, $250.00\n\n### Volume Analysis\n- **Average Daily Volume**: 51.2M shares\n- **Recent Volume Trend**: Above average, indicating strong institutional interest\n- **Volume Spike Days**: Nov 5 (55.1M), Nov 15 (54.3M)\n\n### Key Indicators\n- **RSI (14)**: 68.5 - Approaching overbought but still healthy\n- **MACD**: Bullish crossover signal on Nov 8\n- **Moving Averages**: Price above 20-day SMA ($232.40) and 50-day SMA ($228.15)\n\n## Market Context\n\nThe broader tech sector has shown resilience with NASDAQ up 4.2% over the same period. AAPL is outperforming the index, demonstrating relative strength.\n\n## Conclusion\n\nTechnical setup remains constructive with room for further upside to $245-250 range. Watch for consolidation near $240 level before next leg higher." + }, + "sentiment_report": { + "section_name": "sentiment_report", + "analyst": "sentiment", + "generated_at": "2024-12-26T14:26:30", + "content": "# Social Sentiment Analysis for AAPL\n\n## Sentiment Overview\n\n**Overall Sentiment Score**: 7.8/10 (Positive)\n\n### Platform Breakdown\n\n#### Reddit (r/wallstreetbets, r/stocks, r/investing)\n- **Sentiment**: Bullish (72% positive mentions)\n- **Volume**: 3,450 mentions in past 7 days\n- **Key Themes**:\n - iPhone 15 sales exceeding expectations in Asian markets\n - Services revenue growth narrative gaining traction\n - Anticipation for Vision Pro launch\n - Institutional accumulation noted\n\n#### Twitter/X Financial Community\n- **Sentiment**: Moderately Bullish (65% positive)\n- **Volume**: 12,800 mentions\n- **Key Influencers**: Positive commentary from @TechAnalyst, @MarketGuru\n- **Trending Topics**:\n - #AAPL breaking resistance\n - AI integration in upcoming iOS release\n - Supply chain optimization\n\n#### StockTwits\n- **Sentiment Score**: 8.2/10\n- **Message Volume**: 5,600 messages\n- **Bull/Bear Ratio**: 3.2:1\n- **Trending**: $AAPL consistently in top 10 trending stocks\n\n## Narrative Analysis\n\n### Positive Drivers\n1. **Product Cycle**: iPhone 15 Pro Max inventory constraints indicating strong demand\n2. **Services Growth**: Recurring revenue model increasingly appreciated\n3. **AI Integration**: Apple Intelligence features generating excitement\n4. **Financial Strength**: $162B cash position provides optionality\n\n### Concerns\n1. **Valuation**: P/E of 28.5x slightly above historical average\n2. **China Risk**: Regulatory concerns in key market\n3. **Competition**: Android market share gains in emerging markets\n\n## Conclusion\n\nSentiment remains constructive with institutional and retail investors aligned on bullish thesis. Positive momentum likely to persist barring macro headwinds." + }, + "news_report": { + "section_name": "news_report", + "analyst": "news", + "generated_at": "2024-12-26T14:28:15", + "content": "# News Analysis for AAPL\n\n## Recent Headlines (Past 7 Days)\n\n### Major Positive News\n\n**Nov 20**: *\"Apple Supplier Foxconn Reports Record Revenue on Strong iPhone Demand\"*\n- Source: Bloomberg\n- Impact: Bullish\n- Key Points: Q4 revenue up 15% YoY, indicating robust iPhone 15 sales\n\n**Nov 22**: *\"Apple Services Revenue Hits All-Time High in Q4\"*\n- Source: Wall Street Journal\n- Impact: Very Bullish\n- Key Points: Services grew 16% YoY to $22.3B, margins expanding\n\n**Nov 24**: *\"Morgan Stanley Raises AAPL Price Target to $250\"*\n- Source: CNBC\n- Impact: Bullish\n- Key Points: Analyst upgrade citing AI opportunity and services strength\n\n### Neutral/Mixed News\n\n**Nov 23**: *\"Apple Faces Regulatory Scrutiny in EU Over App Store Practices\"*\n- Source: Reuters\n- Impact: Neutral\n- Key Points: Ongoing regulatory dialogue, no immediate financial impact expected\n\n### Product & Technology Updates\n\n**Nov 21**: *\"Apple Vision Pro SDK Downloads Surge Among Developers\"*\n- Source: TechCrunch\n- Impact: Positive Long-term\n- Key Points: 50,000+ developers experimenting with spatial computing platform\n\n**Nov 25**: *\"Apple AI Researchers Publish Breakthrough Paper on On-Device LLMs\"*\n- Source: The Information\n- Impact: Positive\n- Key Points: Demonstrates leadership in privacy-preserving AI\n\n## Sector & Macro Context\n\n- Tech sector benefiting from Fed pause narrative\n- Holiday shopping season off to strong start\n- Consumer spending resilient despite inflation concerns\n\n## News Sentiment Score: 8.5/10\n\nOverwhelmingly positive news flow with product strength, services growth, and analyst support. Regulatory headwinds manageable and priced in." + }, + "fundamentals_report": { + "section_name": "fundamentals_report", + "analyst": "fundamentals", + "generated_at": "2024-12-26T14:30:00", + "content": "# Fundamental Analysis for AAPL\n\n## Valuation Metrics (as of 2024-12-26)\n\n### Current Valuation\n- **Market Cap**: $3.68T\n- **Share Price**: $239.25\n- **P/E Ratio**: 28.5x (vs sector avg 24.2x)\n- **Forward P/E**: 26.3x\n- **PEG Ratio**: 2.1\n- **Price/Sales**: 7.8x\n- **Price/Book**: 42.3x\n- **EV/EBITDA**: 21.5x\n\n## Financial Performance\n\n### Revenue Analysis (TTM)\n- **Total Revenue**: $383.9B (up 2.1% YoY)\n- **Products**: $298.1B (flat YoY)\n- **Services**: $85.8B (up 16.2% YoY)\n- **Gross Margin**: 44.5% (expanding)\n- **Operating Margin**: 29.8%\n\n### Geographic Revenue Mix\n- Americas: 42% ($161.2B)\n- Europe: 24% ($92.1B)\n- Greater China: 19% ($72.9B)\n- Japan: 6% ($23.0B)\n- Rest of Asia Pacific: 9% ($34.7B)\n\n### Profitability\n- **Net Income (TTM)**: $96.8B\n- **Operating Income**: $114.3B\n- **Free Cash Flow**: $99.2B\n- **ROE**: 147.5%\n- **ROA**: 27.8%\n- **ROIC**: 52.3%\n\n## Balance Sheet Strength\n\n### Assets\n- **Cash & Equivalents**: $162.1B\n- **Total Current Assets**: $142.2B\n- **Total Assets**: $352.8B\n\n### Liabilities & Equity\n- **Total Debt**: $106.6B\n- **Net Cash**: $55.5B\n- **Current Ratio**: 0.98\n- **Debt/Equity**: 1.52\n\n## Capital Allocation\n\n### Shareholder Returns (Past 12 Months)\n- **Dividends Paid**: $15.0B (0.96% yield)\n- **Share Buybacks**: $77.5B\n- **Total Returned**: $92.5B (93% of FCF)\n\n### Dividend Profile\n- **Annual Dividend**: $0.96/share\n- **Dividend Yield**: 0.40%\n- **5-Year Growth Rate**: 6.2% CAGR\n- **Payout Ratio**: 15.5%\n\n## Growth Outlook\n\n### Revenue Projections\n- **FY 2025E**: $401.2B (4.5% growth)\n- **FY 2026E**: $420.8B (4.9% growth)\n\n### Key Growth Drivers\n1. **Services**: High-margin recurring revenue (16%+ growth)\n2. **India Market**: Fastest growing geography (20%+ growth)\n3. **Wearables**: Apple Watch and AirPods category expansion\n4. **Vision Pro**: New product category (long-term)\n5. **AI Features**: Differentiation driving upgrade cycles\n\n## Investment Quality\n\n### Strengths\n- Fortress balance sheet with net cash position\n- Best-in-class margins and capital efficiency\n- Recurring services revenue provides stability\n- Ecosystem lock-in creates moat\n- World-class brand value\n\n### Risks\n- High valuation vs historical averages\n- China geopolitical risk\n- Regulatory pressures (App Store, privacy)\n- Product cycle maturity in smartphones\n- Competition in services (Spotify, Google)\n\n## Fundamental Score: 9.2/10\n\nExceptional business quality with strong competitive positioning, but valuation is full. Quality justifies premium multiple." + }, + "investment_plan": { + "section_name": "investment_plan", + "analyst": "investment_manager", + "generated_at": "2024-12-26T14:31:45", + "content": "# Investment Plan for AAPL\n\n## Investment Thesis\n\nApple represents a high-quality growth company transitioning to a services-led business model. The combination of strong technical setup, positive sentiment, favorable news flow, and solid fundamentals supports a **BUY** recommendation for long-term investors.\n\n## Recommendation: BUY\n\n### Target Allocations\n\n**Conservative Portfolio (Core Holding)**\n- Allocation: 4-5% of portfolio\n- Rationale: Lower volatility than broad tech sector, dividend income\n- Time Horizon: 5+ years\n\n**Growth Portfolio (Satellite Position)**\n- Allocation: 6-8% of portfolio\n- Rationale: Services growth, AI opportunity, ecosystem expansion\n- Time Horizon: 3-5 years\n\n**Aggressive Growth Portfolio**\n- Allocation: 8-10% of portfolio\n- Rationale: Technical momentum, product cycle upside\n- Time Horizon: 1-3 years\n\n## Entry Strategy\n\n### Recommended Entry Levels\n\n**Primary Entry Zone**: $235-238\n- Accumulate 60% of target position\n- Current price offers good risk/reward\n\n**Secondary Entry Zone**: $230-233 (on pullback)\n- Accumulate remaining 40%\n- Provides better margin of safety\n\n**Scaling Strategy**\n- Divide capital into 3 tranches\n- Enter first 1/3 at current levels\n- Add second 1/3 on any dip to $233-235\n- Reserve final 1/3 for deeper pullback to $228-230\n\n## Price Targets\n\n### Near-term (3-6 months)\n- **Target**: $250-255\n- **Upside**: 4.5-6.6%\n- **Catalyst**: Continued iPhone 15 strength, services acceleration\n\n### Medium-term (12 months)\n- **Target**: $265-275\n- **Upside**: 10.8-14.9%\n- **Catalyst**: FY2025 earnings growth, AI features launch, Vision Pro adoption\n\n### Long-term (24 months)\n- **Target**: $290-310\n- **Upside**: 21.2-29.6%\n- **Catalyst**: Services reaching $100B annually, India market penetration, new product categories\n\n## Risk Management\n\n### Stop Loss Levels\n\n**Conservative**\n- Hard Stop: $220 (-8.1%)\n- Trailing Stop: 10% from peak\n\n**Moderate**\n- Hard Stop: $215 (-10.2%)\n- Trailing Stop: 12% from peak\n\n**Aggressive**\n- Hard Stop: $210 (-12.2%)\n- Trailing Stop: 15% from peak\n\n### Position Sizing\n- Start with 50% of target allocation\n- Add on confirmed breakout above $242\n- Reduce by 50% if breaks below $232\n- Exit entirely if breaks $220\n\n## Catalysts to Monitor\n\n### Positive Catalysts (Bullish)\n- Q1 2025 earnings beat (Jan 2025)\n- Vision Pro launch success\n- Services revenue acceleration above 18%\n- New AI features announcement\n- Share buyback authorization increase\n- Analyst upgrades from major banks\n\n### Negative Catalysts (Risk Factors)\n- China regulatory action\n- iPhone 15 demand disappointment\n- Services growth deceleration\n- Break below 200-day moving average ($225)\n- Fed hawkish pivot\n- Recession indicators\n\n## Portfolio Construction Suggestions\n\n### Complementary Holdings\n- **Pair with**: MSFT, GOOGL (diversified mega-cap tech)\n- **Hedge with**: QQQ puts or VIX calls (tail risk protection)\n- **Balance with**: Consumer staples (XLP) or utilities (XLU)\n\n### Tax Considerations\n- Low dividend yield minimizes tax drag\n- Consider tax-loss harvesting if position drops >10%\n- Long-term capital gains favorable after 1-year holding period\n\n## Review Schedule\n\n- **Weekly**: Technical chart review, news monitoring\n- **Monthly**: Position sizing adjustment based on performance\n- **Quarterly**: Fundamental reassessment post-earnings\n- **Annual**: Strategic allocation review\n\n## Conclusion\n\nAAPL offers attractive risk/reward for quality-focused investors. Initiate position in $235-238 range with 12-month target of $265. Strong buy on any pullback to $230-233 support zone.\n\n**Overall Rating**: STRONG BUY\n**Confidence Level**: 8.5/10\n**Risk/Reward Ratio**: 1:3.2" + }, + "trader_investment_plan": { + "section_name": "trader_investment_plan", + "analyst": "trader", + "generated_at": "2024-12-26T14:33:20", + "content": "# Trading Plan for AAPL\n\n## Trade Setup\n\n**Trade Type**: Swing Trade (Bullish Momentum)\n**Timeframe**: 2-4 weeks\n**Bias**: LONG\n\n## Entry Strategy\n\n### Entry Conditions (ALL must be met)\n1. Price holds above $237.50 (20-day SMA support)\n2. Volume > 50M shares (above average)\n3. RSI between 55-70 (not overbought)\n4. MACD remains bullish\n5. Market breadth positive (NASDAQ up)\n\n### Specific Entry Levels\n\n**Preferred Entry**: $238.00-238.50\n- Just above current consolidation\n- Good risk/reward to $245 target\n- Tight stop below $237\n\n**Alternative Entry (Breakout)**: $240.25-240.50\n- Confirmed break of resistance\n- Higher probability but wider stop\n- Initial target $247\n\n**Alternative Entry (Pullback)**: $235.50-236.00\n- Retest of support zone\n- Best risk/reward ratio\n- Tighter stop possible at $234.50\n\n## Position Sizing\n\n### Risk Parameters\n- **Risk per Trade**: 1% of trading capital\n- **Account Size**: $100,000 (example)\n- **Risk Amount**: $1,000\n- **Stop Distance**: $2.50 (from $238.50 to $236.00)\n- **Position Size**: 400 shares ($95,400)\n\n### Scaling Strategy\n- Enter 50% at $238.50\n- Add 30% on breakout above $240\n- Add final 20% on pullback to $237 (if occurs)\n\n## Exit Strategy\n\n### Profit Targets (Scale Out)\n\n**Target 1**: $242.00 (+1.5%)\n- Take profit on 30% of position\n- Move stop to breakeven on remaining\n- Lock in 0.45% account gain\n\n**Target 2**: $245.00 (+2.7%)\n- Take profit on additional 40% of position\n- Move stop to $240 on remaining\n- Total locked in: 1.35% account gain\n\n**Target 3**: $248.50 (+4.2%)\n- Exit remaining 30% of position\n- Or trail with 12-day EMA\n- Maximum potential: 2.1% account gain\n\n### Stop Loss Levels\n\n**Initial Stop**: $236.00\n- Hard stop for all shares\n- -1.05% loss on position\n- Equals 1% account risk\n\n**Trailing Stop (after T1 hit)**\n- 12-period EMA on hourly chart\n- Or $2 below intraday high\n- Whichever is tighter\n\n**Break-even Stop (after T2 hit)**\n- Move to entry price $238.50\n- No-loss scenario guaranteed\n- Let winners run\n\n## Risk Management Rules\n\n### Trade Management\n1. Never add to losing position\n2. Never move stop further away\n3. Cut losses at -1% account value\n4. Let winners run past T3 if momentum strong\n5. Review position end of each trading day\n\n### Exit Triggers (Immediate Exit)\n- Break below $236 stop loss\n- RSI drops below 40 on daily\n- MACD bearish crossover\n- Market flash crash (NASDAQ -2%)\n- Negative breaking news\n\n### Partial Exit Triggers\n- Extended hours >2% move either direction\n- Volume dries up below 30M\n- Price stalls at resistance for 3+ days\n\n## Time-based Exits\n\n- **Maximum Hold**: 30 calendar days\n- **Review Point**: 14 days (reassess if no progress)\n- **Weekend Hold**: Yes (confirmed uptrend only)\n- **Earnings**: Exit 2 days before if in profit\n\n## Technical Setup Confirmation\n\n### Entry Checklist\n- [ ] Daily candle: Bullish pattern\n- [ ] Volume: Above 50M\n- [ ] RSI: 55-70 range\n- [ ] MACD: Bullish and rising\n- [ ] Support: $237.50 holding\n- [ ] Market: NASDAQ positive\n- [ ] Sector: XLK outperforming SPY\n\n### Daily Monitoring\n- Pre-market: News scan, futures check\n- Open: Watch first 30-min for direction\n- Mid-day: Update stops if targets hit\n- Close: Note closing price vs VWAP\n- After-hours: Monitor for news\n\n## Scenarios & Responses\n\n### Scenario 1: Gap Up Open (+1.5%)\n- **Action**: Wait for pullback to VWAP or $240\n- **Rationale**: Avoid chasing, better entry coming\n\n### Scenario 2: Gap Down Open (-1%)\n- **Action**: Cancel trade setup, reassess\n- **Rationale**: Setup invalidated, wait for new signal\n\n### Scenario 3: Slow Grind Higher\n- **Action**: Trail stop tighter, take profits at T3\n- **Rationale**: Momentum weakening, secure gains\n\n### Scenario 4: Sharp Rally to $245\n- **Action**: Sell half, trail remainder aggressively\n- **Rationale**: Take profits on strength, let runner work\n\n### Scenario 5: Whipsaw at $238\n- **Action**: Reduce size by 50% if stopped, wait for clear direction\n- **Rationale**: Unclear setup, preserve capital\n\n## Trade Journal Template\n\n**Entry Log**:\n- Date/Time: ___________\n- Entry Price: ___________\n- Position Size: ___________\n- Stop Loss: ___________\n- Targets: T1___ T2___ T3___\n- Market Conditions: ___________\n- Conviction Level (1-10): ___________\n\n**Exit Log**:\n- Date/Time: ___________\n- Exit Price: ___________\n- Reason: ___________\n- P&L: $_____ (_____% )\n- Lessons: ___________\n\n## Performance Targets\n\n- **Win Rate Target**: 55%+\n- **Avg Win**: 2.5%+\n- **Avg Loss**: -1.0%\n- **Risk/Reward**: Minimum 2:1\n- **Expectancy**: Positive\n\n## Conclusion\n\nThis is a high-probability setup with defined risk. Execute with discipline, manage position actively, and don't overtrade. One quality setup is worth more than five mediocre ones.\n\n**Trade Rating**: A-\n**Setup Quality**: 8/10\n**Execute if**: All entry conditions met" + }, + "final_trade_decision": { + "section_name": "final_trade_decision", + "analyst": "chief_investment_officer", + "generated_at": "2024-12-26T14:35:00", + "content": "# Final Trade Decision for AAPL\n\n## Executive Decision: EXECUTE BUY ORDER\n\n**Action**: Open long position in AAPL\n**Timing**: Initiate within next 2 trading days\n**Confidence**: 85%\n\n## Position Details\n\n### Order Specifications\n- **Ticker**: AAPL\n- **Action**: BUY TO OPEN\n- **Quantity**: 400 shares\n- **Order Type**: LIMIT ORDER\n- **Limit Price**: $238.50\n- **Time in Force**: DAY (renew daily if not filled)\n- **Account Value**: $100,000 (example)\n- **Position Value**: $95,400 (3.95% of portfolio)\n\n### Alternative Order (If Primary Not Filled)\n- **Limit Price**: $239.00 (next day)\n- **Maximum Price**: $240.00 (do not chase above)\n\n## Risk Parameters\n\n- **Initial Stop Loss**: $236.00 per share\n- **Risk per Share**: $2.50\n- **Total Risk**: $1,000 (1.0% of account)\n- **Position Risk**: 1.05% of position value\n\n## Rationale Summary\n\nThis decision synthesizes analysis from:\n1. **Market Analysis**: Strong bullish technicals, confirmed uptrend\n2. **Sentiment Analysis**: Positive retail and institutional sentiment\n3. **News Analysis**: Favorable news flow, analyst support\n4. **Fundamental Analysis**: High quality business, reasonable valuation\n5. **Investment Strategy**: Aligns with growth portfolio objectives\n6. **Trading Strategy**: High-probability technical setup\n\n### Key Decision Factors\n- βœ… All technical indicators aligned (trend, momentum, volume)\n- βœ… Fundamental quality justifies position\n- βœ… Sentiment supports continuation\n- βœ… Risk/reward ratio favorable (1:3+)\n- βœ… Position sizing appropriate (1% account risk)\n- βœ… Clear exit strategy defined\n\n## Execution Instructions\n\n### Pre-Trade Checklist\n- [ ] Confirm account has sufficient buying power\n- [ ] Verify limit order settings correct\n- [ ] Set stop-loss order (GTC, stop $236.00)\n- [ ] Set profit target alerts (T1: $242, T2: $245, T3: $248.50)\n- [ ] Document trade in journal\n- [ ] Review position sizing calculation\n\n### Day 1 Execution Plan (2024-12-27)\n\n**Pre-Market (9:00-9:30 AM ET)**\n- Monitor pre-market price action\n- Check for overnight news\n- Confirm market conditions stable\n- Review futures (NQ, SPY)\n\n**Market Open (9:30 AM ET)**\n- Do NOT enter in first 15 minutes\n- Wait for opening volatility to settle\n- Observe support at $237.50\n\n**Optimal Entry Window (9:45-10:30 AM ET)**\n- Place limit order at $238.50\n- If price gaps above $240, stand aside\n- If price below $237, wait for recovery\n\n**Mid-Day Review (12:00 PM ET)**\n- If unfilled, assess price action\n- Adjust limit to $239.00 if consolidating higher\n- Cancel order if breaks below $237\n\n**Close (3:45-4:00 PM ET)**\n- If still unfilled, cancel order\n- Prepare for next-day attempt\n- Update limit price for Day 2 if appropriate\n\n### Day 2 Execution Plan (If Needed)\n- Adjust limit to $239.00-239.50 range\n- Same execution protocol as Day 1\n- Maximum chase price: $240.00\n\n## Post-Entry Management\n\n### Immediate Actions (Within 1 Hour)\n1. Confirm fill at desired price\n2. Enter stop-loss order at $236.00 (GTC)\n3. Set price alerts for targets and stop\n4. Document entry in trade journal\n5. Calculate actual risk and verify 1% account\n\n### First 24 Hours\n- Monitor for quick profit opportunity (>1%)\n- Watch for reversal signals\n- Confirm stop order is active\n- Review end-of-day price action vs VWAP\n\n### First Week\n- Daily: Check price relative to entry and stop\n- Monitor news flow for AAPL\n- Watch for Target 1 ($242) approach\n- Be prepared to take partial profits\n\n## Contingency Plans\n\n### If Stopped Out ($236)\n- Accept loss ($1,000 / 1% account)\n- Wait minimum 2 days before re-entry\n- Reassess setup with fresh eyes\n- Do NOT revenge trade\n- Journal lessons learned\n\n### If Quick Profit (T1 in <3 days)\n- Take 30% profit as planned\n- Move stop to breakeven\n- Let remainder run to T2/T3\n- Update journal with success factors\n\n### If Consolidates (No progress in 7 days)\n- Review setup validity\n- Consider taking small loss/breakeven\n- Opportunity cost vs other setups\n- Maximum hold: 30 days\n\n### If Negative News Breaks\n- Assess materiality and impact\n- Exit immediately if fundamental negative\n- Hold if temporary/technical issue\n- When in doubt, take profit/small loss and reassess\n\n## Performance Monitoring\n\n### Key Metrics to Track\n- Entry price vs current price (P&L)\n- Distance from stop (risk remaining)\n- Progress toward targets (% complete)\n- Time in trade (vs 30-day max)\n- Opportunity cost (vs other trades)\n\n### Decision Review Points\n\n**Day 7**: Mini-review\n- Is trade progressing as expected?\n- Any new information changes thesis?\n- Should stop be tightened?\n\n**Day 14**: Full review\n- Re-run technical analysis\n- Check if catalysts materialized\n- Adjust targets/stops if needed\n- Consider exit if stalled\n\n**Day 30**: Maximum hold review\n- Exit if targets not hit\n- Take profit or small loss\n- Free capital for new opportunities\n\n## Alignment Check\n\n### Investment Policy Alignment\n- βœ… Within single-stock concentration limits (<5%)\n- βœ… Sector exposure reasonable (Tech <30%)\n- βœ… Risk per trade at policy limit (1%)\n- βœ… Quality criteria met (Fundamentals 9.2/10)\n- βœ… Portfolio diversification maintained\n\n### Risk Management Alignment\n- βœ… Position sizing formula followed\n- βœ… Stop loss mandatory and in place\n- βœ… Profit targets defined\n- βœ… Time stop set (30 days)\n- βœ… Emergency exit rules clear\n\n## Final Authorization\n\n**Approved by**: Chief Investment Officer\n**Date**: 2024-12-26\n**Authorization Code**: AAPL-BUY-20241226-001\n**Next Review**: 2024-12-27 (post-entry) or 2024-12-30 (if unfilled)\n\n---\n\n## EXECUTE TRADE\n\n**Start Date**: 2024-12-27\n**End Date**: 2025-01-26 (max)\n**Initial Risk**: $1,000 (1.0%)\n**Target Profit**: $3,200+ (3.2%+)\n**Risk/Reward**: 1:3.2\n\n**Trade Status**: APPROVED - READY FOR EXECUTION\n\n**GL & HF** 🎯" + } + }, + "partial_sections": { + "description": "Example of partial report with only some sections completed", + "market_report": "# Market Analysis for TSLA\n\nTSLA shows mixed signals with consolidation near $267...", + "sentiment_report": null, + "news_report": "# News Analysis for TSLA\n\nRecent Cybertruck delivery event...", + "fundamentals_report": null, + "investment_plan": null, + "trader_investment_plan": null, + "final_trade_decision": null + } +} diff --git a/tests/fixtures/stock_data/cn_market_ohlcv.json b/tests/fixtures/stock_data/cn_market_ohlcv.json new file mode 100644 index 00000000..6dd2ac3a --- /dev/null +++ b/tests/fixtures/stock_data/cn_market_ohlcv.json @@ -0,0 +1,119 @@ +{ + "description": "Chinese stock market OHLCV data for 600519.SH (θ΄΅ε·žθŒ…ε° - Kweichow Moutai)", + "ticker": "600519.SH", + "market": "CN", + "date_range": "2024-11-01 to 2024-11-15", + "note": "Chinese market data uses localized column names", + "data": [ + { + "ζ—₯期": "2024-11-01", + "εΌ€η›˜": 1680.50, + "ζœ€ι«˜": 1695.80, + "ζœ€δ½Ž": 1675.20, + "ζ”Άη›˜": 1688.30, + "ζˆδΊ€ι‡": 12450000 + }, + { + "ζ—₯期": "2024-11-04", + "εΌ€η›˜": 1688.30, + "ζœ€ι«˜": 1702.40, + "ζœ€δ½Ž": 1682.10, + "ζ”Άη›˜": 1698.75, + "ζˆδΊ€ι‡": 13280000 + }, + { + "ζ—₯期": "2024-11-05", + "εΌ€η›˜": 1698.75, + "ζœ€ι«˜": 1710.20, + "ζœ€δ½Ž": 1692.50, + "ζ”Άη›˜": 1705.40, + "ζˆδΊ€ι‡": 14150000 + }, + { + "ζ—₯期": "2024-11-06", + "εΌ€η›˜": 1705.40, + "ζœ€ι«˜": 1718.60, + "ζœ€δ½Ž": 1700.30, + "ζ”Άη›˜": 1712.80, + "ζˆδΊ€ι‡": 12890000 + }, + { + "ζ—₯期": "2024-11-07", + "εΌ€η›˜": 1712.80, + "ζœ€ι«˜": 1725.90, + "ζœ€δ½Ž": 1708.40, + "ζ”Άη›˜": 1720.15, + "ζˆδΊ€ι‡": 13560000 + }, + { + "ζ—₯期": "2024-11-08", + "εΌ€η›˜": 1720.15, + "ζœ€ι«˜": 1732.50, + "ζœ€δ½Ž": 1715.20, + "ζ”Άη›˜": 1728.60, + "ζˆδΊ€ι‡": 14320000 + }, + { + "ζ—₯期": "2024-11-11", + "εΌ€η›˜": 1728.60, + "ζœ€ι«˜": 1740.80, + "ζœ€δ½Ž": 1722.90, + "ζ”Άη›˜": 1735.25, + "ζˆδΊ€ι‡": 13780000 + }, + { + "ζ—₯期": "2024-11-12", + "εΌ€η›˜": 1735.25, + "ζœ€ι«˜": 1748.30, + "ζœ€δ½Ž": 1730.10, + "ζ”Άη›˜": 1742.90, + "ζˆδΊ€ι‡": 14890000 + }, + { + "ζ—₯期": "2024-11-13", + "εΌ€η›˜": 1742.90, + "ζœ€ι«˜": 1755.60, + "ζœ€δ½Ž": 1738.20, + "ζ”Άη›˜": 1750.45, + "ζˆδΊ€ι‡": 13450000 + }, + { + "ζ—₯期": "2024-11-14", + "εΌ€η›˜": 1750.45, + "ζœ€ι«˜": 1762.80, + "ζœ€δ½Ž": 1745.30, + "ζ”Άη›˜": 1758.20, + "ζˆδΊ€ι‡": 14670000 + }, + { + "ζ—₯期": "2024-11-15", + "εΌ€η›˜": 1758.20, + "ζœ€ι«˜": 1770.50, + "ζœ€δ½Ž": 1752.90, + "ζ”Άη›˜": 1765.35, + "ζˆδΊ€ι‡": 15120000 + } + ], + "column_mapping": { + "ζ—₯期": "Date", + "εΌ€η›˜": "Open", + "ζœ€ι«˜": "High", + "ζœ€δ½Ž": "Low", + "ζ”Άη›˜": "Close", + "ζˆδΊ€ι‡": "Volume" + }, + "edge_cases": { + "description": "Edge case scenarios with Chinese column names", + "empty_data": [], + "mixed_columns": [ + { + "ζ—₯期": "2024-11-01", + "εΌ€η›˜": 1680.50, + "ζœ€ι«˜": 1695.80, + "ζœ€δ½Ž": 1675.20, + "Close": 1688.30, + "Volume": 12450000 + } + ] + } +} diff --git a/tests/fixtures/stock_data/standardized_ohlcv.json b/tests/fixtures/stock_data/standardized_ohlcv.json new file mode 100644 index 00000000..a7f797c8 --- /dev/null +++ b/tests/fixtures/stock_data/standardized_ohlcv.json @@ -0,0 +1,110 @@ +{ + "description": "Standardized OHLCV data after processing (all English column names, Date as index)", + "ticker": "TSLA", + "market": "US", + "date_range": "2024-11-01 to 2024-11-10", + "note": "This represents data after standardization - ready for technical analysis", + "data": [ + { + "Date": "2024-11-01T00:00:00", + "Open": 242.84, + "High": 248.50, + "Low": 241.05, + "Close": 246.60, + "Volume": 95234000 + }, + { + "Date": "2024-11-04T00:00:00", + "Open": 246.60, + "High": 251.20, + "Low": 244.80, + "Close": 249.85, + "Volume": 87654000 + }, + { + "Date": "2024-11-05T00:00:00", + "Open": 249.85, + "High": 254.30, + "Low": 247.90, + "Close": 252.40, + "Volume": 92341000 + }, + { + "Date": "2024-11-06T00:00:00", + "Open": 252.40, + "High": 256.75, + "Low": 250.60, + "Close": 255.10, + "Volume": 89123000 + }, + { + "Date": "2024-11-07T00:00:00", + "Open": 255.10, + "High": 259.40, + "Low": 253.20, + "Close": 257.80, + "Volume": 91456000 + }, + { + "Date": "2024-11-08T00:00:00", + "Open": 257.80, + "High": 261.90, + "Low": 255.70, + "Close": 260.25, + "Volume": 93782000 + }, + { + "Date": "2024-11-11T00:00:00", + "Open": 260.25, + "High": 264.50, + "Low": 258.40, + "Close": 262.95, + "Volume": 88567000 + }, + { + "Date": "2024-11-12T00:00:00", + "Open": 262.95, + "High": 267.10, + "Low": 260.80, + "Close": 265.40, + "Volume": 90234000 + }, + { + "Date": "2024-11-13T00:00:00", + "Open": 265.40, + "High": 269.60, + "Low": 263.50, + "Close": 267.85, + "Volume": 94567000 + }, + { + "Date": "2024-11-14T00:00:00", + "Open": 267.85, + "High": 271.90, + "Low": 265.70, + "Close": 270.30, + "Volume": 96890000 + } + ], + "technical_indicators_example": { + "description": "Example of additional technical indicators that might be added", + "data": [ + { + "Date": "2024-11-14T00:00:00", + "Open": 267.85, + "High": 271.90, + "Low": 265.70, + "Close": 270.30, + "Volume": 96890000, + "SMA_20": 258.45, + "EMA_20": 260.12, + "RSI_14": 68.5, + "MACD": 2.34, + "MACD_signal": 1.89, + "BB_upper": 275.20, + "BB_middle": 258.45, + "BB_lower": 241.70 + } + ] + } +} diff --git a/tests/fixtures/stock_data/us_market_ohlcv.json b/tests/fixtures/stock_data/us_market_ohlcv.json new file mode 100644 index 00000000..d77eb86e --- /dev/null +++ b/tests/fixtures/stock_data/us_market_ohlcv.json @@ -0,0 +1,137 @@ +{ + "description": "US stock market OHLCV data for AAPL (Apple Inc.)", + "ticker": "AAPL", + "market": "US", + "date_range": "2024-11-01 to 2024-11-15", + "data": [ + { + "Date": "2024-11-01T00:00:00", + "Open": 225.50, + "High": 228.75, + "Low": 224.10, + "Close": 227.85, + "Volume": 52341000 + }, + { + "Date": "2024-11-04T00:00:00", + "Open": 227.85, + "High": 230.20, + "Low": 226.50, + "Close": 229.95, + "Volume": 48762000 + }, + { + "Date": "2024-11-05T00:00:00", + "Open": 229.95, + "High": 232.40, + "Low": 228.80, + "Close": 231.20, + "Volume": 55123000 + }, + { + "Date": "2024-11-06T00:00:00", + "Open": 231.20, + "High": 233.15, + "Low": 230.00, + "Close": 232.80, + "Volume": 51890000 + }, + { + "Date": "2024-11-07T00:00:00", + "Open": 232.80, + "High": 234.50, + "Low": 231.95, + "Close": 233.75, + "Volume": 49234000 + }, + { + "Date": "2024-11-08T00:00:00", + "Open": 233.75, + "High": 235.90, + "Low": 232.40, + "Close": 234.55, + "Volume": 53678000 + }, + { + "Date": "2024-11-11T00:00:00", + "Open": 234.55, + "High": 236.20, + "Low": 233.10, + "Close": 235.80, + "Volume": 47892000 + }, + { + "Date": "2024-11-12T00:00:00", + "Open": 235.80, + "High": 237.45, + "Low": 234.90, + "Close": 236.90, + "Volume": 50123000 + }, + { + "Date": "2024-11-13T00:00:00", + "Open": 236.90, + "High": 238.30, + "Low": 235.50, + "Close": 237.15, + "Volume": 48567000 + }, + { + "Date": "2024-11-14T00:00:00", + "Open": 237.15, + "High": 239.00, + "Low": 236.20, + "Close": 238.45, + "Volume": 52890000 + }, + { + "Date": "2024-11-15T00:00:00", + "Open": 238.45, + "High": 240.10, + "Low": 237.30, + "Close": 239.25, + "Volume": 54321000 + } + ], + "edge_cases": { + "description": "Edge case scenarios for testing", + "empty_data": [], + "single_row": [ + { + "Date": "2024-11-01T00:00:00", + "Open": 225.50, + "High": 228.75, + "Low": 224.10, + "Close": 227.85, + "Volume": 52341000 + } + ], + "missing_volume": [ + { + "Date": "2024-11-01T00:00:00", + "Open": 225.50, + "High": 228.75, + "Low": 224.10, + "Close": 227.85 + } + ], + "out_of_order_dates": [ + { + "Date": "2024-11-05T00:00:00", + "Open": 229.95, + "High": 232.40, + "Low": 228.80, + "Close": 231.20, + "Volume": 55123000 + }, + { + "Date": "2024-11-01T00:00:00", + "Open": 225.50, + "High": 228.75, + "Low": 224.10, + "Close": 227.85, + "Volume": 52341000 + } + ] + } +}