TradingAgents/tests/fixtures/README.md

596 lines
16 KiB
Markdown

# 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