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:
parent
edae1ab2cc
commit
b4653ca37b
12
CHANGELOG.md
12
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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue