feat(tests): add test fixtures directory with mock data - Fixes #51

- 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 <noreply@anthropic.com>
This commit is contained in:
Andrew Kaszubski 2025-12-26 11:23:29 +11:00
parent edae1ab2cc
commit b4653ca37b
11 changed files with 2271 additions and 0 deletions

View File

@ -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

View File

@ -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:

595
tests/fixtures/README.md vendored Normal file
View File

@ -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"])) # <class 'datetime.datetime'>
```
### 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

592
tests/fixtures/__init__.py vendored Normal file
View File

@ -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",
]

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -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"]
}
}

File diff suppressed because one or more lines are too long

View File

@ -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
}
]
}
}

View File

@ -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
}
]
}
}

View File

@ -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
}
]
}
}