Local changes: Updated model training, removed debug instrumentation, and configuration improvements
This commit is contained in:
40
docs/developer/README.md
Normal file
40
docs/developer/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Developer Guide
|
||||
|
||||
Welcome to the Crypto Trader developer guide. This guide will help you set up a development environment, understand the codebase, and contribute to the project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Development Setup](setup.md) - Setting up your development environment
|
||||
2. [Architecture Overview](architecture.md) - System architecture and design
|
||||
3. [Coding Standards](coding_standards.md) - Code style and conventions
|
||||
4. [Adding Exchanges](adding_exchanges.md) - How to add new exchange adapters
|
||||
5. [Creating Strategies](creating_strategies.md) - Strategy development guide
|
||||
6. [Testing](testing.md) - Testing guidelines and practices
|
||||
7. [Contributing](contributing.md) - Contribution guidelines
|
||||
8. [Release Process](release_process.md) - Release and deployment process
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. [Set up development environment](setup.md)
|
||||
2. [Review architecture](architecture.md)
|
||||
3. [Read coding standards](coding_standards.md)
|
||||
4. [Run tests](testing.md#running-tests)
|
||||
5. [Make your first contribution](contributing.md)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Write/update tests
|
||||
5. Ensure all tests pass
|
||||
6. Update documentation
|
||||
7. Submit a pull request
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Review the [API Documentation](../api/index.html)
|
||||
- Check existing issues and pull requests
|
||||
- Ask questions in discussions
|
||||
- Review code examples in the codebase
|
||||
|
||||
216
docs/developer/adding_exchanges.md
Normal file
216
docs/developer/adding_exchanges.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Adding New Exchange Adapters
|
||||
|
||||
This guide explains how to add support for new cryptocurrency exchanges.
|
||||
|
||||
## Exchange Adapter Architecture
|
||||
|
||||
All exchange adapters inherit from `BaseExchange` and implement a standardized interface. This allows the trading engine to work with any exchange through a common API.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Create Exchange Module
|
||||
|
||||
Create a new file in `src/exchanges/`:
|
||||
|
||||
```python
|
||||
# src/exchanges/your_exchange.py
|
||||
from src.exchanges.base import BaseExchange
|
||||
from src.exchanges.factory import ExchangeFactory
|
||||
import ccxt.pro as ccxt
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class YourExchangeAdapter(BaseExchange):
|
||||
"""Adapter for Your Exchange."""
|
||||
|
||||
def __init__(self, name: str, api_key: str, secret_key: str, password: str = None):
|
||||
super().__init__(name, api_key, secret_key, password)
|
||||
self.exchange = ccxt.yourexchange({
|
||||
'apiKey': self.api_key,
|
||||
'secret': self.secret_key,
|
||||
'enableRateLimit': True,
|
||||
})
|
||||
|
||||
async def connect(self):
|
||||
"""Establish connection to exchange."""
|
||||
await self.exchange.load_markets()
|
||||
self.is_connected = True
|
||||
logger.info(f"Connected to {self.name}")
|
||||
|
||||
# Implement all required methods from BaseExchange
|
||||
# ...
|
||||
```
|
||||
|
||||
### 2. Implement Required Methods
|
||||
|
||||
Implement all abstract methods from `BaseExchange`:
|
||||
|
||||
- `connect()` - Establish connection
|
||||
- `disconnect()` - Close connection
|
||||
- `fetch_balance()` - Get account balance
|
||||
- `place_order()` - Place order
|
||||
- `cancel_order()` - Cancel order
|
||||
- `fetch_order_status()` - Get order status
|
||||
- `fetch_ohlcv()` - Get historical data
|
||||
- `subscribe_ohlcv()` - Real-time OHLCV
|
||||
- `subscribe_trades()` - Real-time trades
|
||||
- `subscribe_order_book()` - Real-time order book
|
||||
- `fetch_open_orders()` - Get open orders
|
||||
- `fetch_positions()` - Get positions (futures)
|
||||
- `fetch_markets()` - Get available markets
|
||||
|
||||
### 3. Register Exchange
|
||||
|
||||
Register the exchange in `src/exchanges/__init__.py`:
|
||||
|
||||
```python
|
||||
from .your_exchange import YourExchangeAdapter
|
||||
from .factory import ExchangeFactory
|
||||
|
||||
ExchangeFactory.register_exchange("your_exchange", YourExchangeAdapter)
|
||||
```
|
||||
|
||||
### 4. Handle Exchange-Specific Features
|
||||
|
||||
Some exchanges have unique features:
|
||||
|
||||
- **Authentication**: Some exchanges use different auth methods
|
||||
- **Rate Limits**: Respect exchange rate limits
|
||||
- **WebSocket**: Implement exchange-specific WebSocket protocol
|
||||
- **Order Types**: Support exchange-specific order types
|
||||
|
||||
### 5. Write Tests
|
||||
|
||||
Create tests in `tests/unit/exchanges/test_your_exchange.py`:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from src.exchanges.your_exchange import YourExchangeAdapter
|
||||
|
||||
class TestYourExchangeAdapter:
|
||||
"""Tests for Your Exchange adapter."""
|
||||
|
||||
@pytest.fixture
|
||||
def adapter(self):
|
||||
return YourExchangeAdapter(
|
||||
name="test",
|
||||
api_key="test_key",
|
||||
secret_key="test_secret"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connect(self, adapter):
|
||||
"""Test connection."""
|
||||
with patch.object(adapter.exchange, 'load_markets'):
|
||||
await adapter.connect()
|
||||
assert adapter.is_connected
|
||||
|
||||
# Add more tests...
|
||||
```
|
||||
|
||||
## Using CCXT
|
||||
|
||||
Most exchanges can use the `ccxt` library:
|
||||
|
||||
```python
|
||||
import ccxt.pro as ccxt
|
||||
|
||||
exchange = ccxt.pro.yourexchange({
|
||||
'apiKey': api_key,
|
||||
'secret': secret_key,
|
||||
'enableRateLimit': True,
|
||||
})
|
||||
|
||||
# Use ccxt methods
|
||||
balance = await exchange.fetch_balance()
|
||||
order = await exchange.create_order(...)
|
||||
```
|
||||
|
||||
## Exchange-Specific Considerations
|
||||
|
||||
### Authentication
|
||||
|
||||
Some exchanges require additional authentication:
|
||||
|
||||
```python
|
||||
# Example: Exchange requiring passphrase
|
||||
self.exchange = ccxt.coinbase({
|
||||
'apiKey': api_key,
|
||||
'secret': secret_key,
|
||||
'password': passphrase, # Coinbase requires this
|
||||
})
|
||||
```
|
||||
|
||||
### Rate Limits
|
||||
|
||||
Always enable rate limiting:
|
||||
|
||||
```python
|
||||
self.exchange = ccxt.yourexchange({
|
||||
'enableRateLimit': True, # Important!
|
||||
})
|
||||
```
|
||||
|
||||
### WebSocket Support
|
||||
|
||||
For real-time data, implement WebSocket connections:
|
||||
|
||||
```python
|
||||
async def subscribe_ohlcv(self, symbol: str, timeframe: str, callback):
|
||||
"""Subscribe to OHLCV updates."""
|
||||
# Exchange-specific WebSocket implementation
|
||||
await self.exchange.watch_ohlcv(symbol, timeframe, callback)
|
||||
```
|
||||
|
||||
## Testing Your Adapter
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Test all methods with mocked exchange responses:
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_balance(self, adapter):
|
||||
"""Test balance fetching."""
|
||||
mock_balance = {'BTC': {'free': 1.0, 'used': 0.0, 'total': 1.0}}
|
||||
with patch.object(adapter.exchange, 'fetch_balance', return_value=mock_balance):
|
||||
balance = await adapter.fetch_balance()
|
||||
assert 'BTC' in balance
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Test with real exchange (use testnet/sandbox if available):
|
||||
|
||||
```python
|
||||
@pytest.mark.integration
|
||||
async def test_real_connection(self):
|
||||
"""Test real connection (requires API keys)."""
|
||||
adapter = YourExchangeAdapter(...)
|
||||
await adapter.connect()
|
||||
assert adapter.is_connected
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Document your exchange adapter:
|
||||
|
||||
1. Add docstrings to all methods
|
||||
2. Document exchange-specific features
|
||||
3. Add usage examples
|
||||
4. Update API documentation
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Error Handling**: Handle exchange-specific errors
|
||||
2. **Rate Limiting**: Always respect rate limits
|
||||
3. **Retry Logic**: Implement retry for transient failures
|
||||
4. **Logging**: Log important operations
|
||||
5. **Testing**: Test thoroughly before submitting
|
||||
|
||||
## Example: Complete Exchange Adapter
|
||||
|
||||
See `src/exchanges/coinbase.py` for a complete example implementation.
|
||||
|
||||
226
docs/developer/coding_standards.md
Normal file
226
docs/developer/coding_standards.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Coding Standards
|
||||
|
||||
This document outlines the coding standards and conventions for Crypto Trader.
|
||||
|
||||
## Code Style
|
||||
|
||||
### Python Style Guide
|
||||
|
||||
Follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) with the following additions:
|
||||
|
||||
- **Line Length**: Maximum 100 characters
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Imports**: Grouped and sorted (stdlib, third-party, local)
|
||||
- **Naming**:
|
||||
- Classes: `PascalCase`
|
||||
- Functions/Variables: `snake_case`
|
||||
- Constants: `UPPER_SNAKE_CASE`
|
||||
- Private: `_leading_underscore`
|
||||
|
||||
### Code Formatting
|
||||
|
||||
Use `black` for automatic formatting:
|
||||
|
||||
```bash
|
||||
black src/ tests/
|
||||
```
|
||||
|
||||
### Type Hints
|
||||
|
||||
Always use type hints for function signatures:
|
||||
|
||||
```python
|
||||
def calculate_position_size(
|
||||
capital: float,
|
||||
risk_percentage: float
|
||||
) -> float:
|
||||
"""Calculate position size."""
|
||||
return capital * risk_percentage
|
||||
```
|
||||
|
||||
### Docstrings
|
||||
|
||||
Use Google-style docstrings:
|
||||
|
||||
```python
|
||||
def fetch_balance(self) -> Dict[str, Any]:
|
||||
"""Fetches the account balance.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
Dictionary containing balance information with keys:
|
||||
- 'free': Available balance
|
||||
- 'used': Used balance
|
||||
- 'total': Total balance
|
||||
|
||||
Raises:
|
||||
ConnectionError: If exchange connection fails
|
||||
ValueError: If exchange credentials are invalid
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
## File Organization
|
||||
|
||||
### Module Structure
|
||||
|
||||
```python
|
||||
"""Module docstring describing the module."""
|
||||
|
||||
# Standard library imports
|
||||
import os
|
||||
from typing import Dict, List
|
||||
|
||||
# Third-party imports
|
||||
import pandas as pd
|
||||
from sqlalchemy import Column
|
||||
|
||||
# Local imports
|
||||
from ..core.logger import get_logger
|
||||
from .base import BaseClass
|
||||
|
||||
# Constants
|
||||
DEFAULT_VALUE = 100
|
||||
|
||||
# Module-level variables
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Classes
|
||||
class MyClass:
|
||||
"""Class docstring."""
|
||||
pass
|
||||
|
||||
# Functions
|
||||
def my_function():
|
||||
"""Function docstring."""
|
||||
pass
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```python
|
||||
try:
|
||||
result = risky_operation()
|
||||
except SpecificError as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error")
|
||||
raise RuntimeError("Operation failed") from e
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
Use appropriate log levels:
|
||||
|
||||
- **DEBUG**: Detailed information for debugging
|
||||
- **INFO**: General informational messages
|
||||
- **WARNING**: Warning messages
|
||||
- **ERROR**: Error messages
|
||||
- **CRITICAL**: Critical errors
|
||||
|
||||
```python
|
||||
logger.debug("Detailed debug information")
|
||||
logger.info("Operation completed successfully")
|
||||
logger.warning("Deprecated function used")
|
||||
logger.error("Operation failed")
|
||||
logger.critical("System failure")
|
||||
```
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Naming
|
||||
|
||||
```python
|
||||
def test_function_name_should_do_something():
|
||||
"""Test description."""
|
||||
pass
|
||||
|
||||
class TestClassName:
|
||||
"""Test class description."""
|
||||
|
||||
def test_method_should_handle_case(self):
|
||||
"""Test method description."""
|
||||
pass
|
||||
```
|
||||
|
||||
### Test Organization
|
||||
|
||||
- One test class per module
|
||||
- Test methods should be independent
|
||||
- Use fixtures for common setup
|
||||
- Mock external dependencies
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Inline Comments
|
||||
|
||||
```python
|
||||
# Calculate position size using Kelly Criterion
|
||||
# Formula: f = (bp - q) / b
|
||||
fraction = (payout_ratio * win_prob - loss_prob) / payout_ratio
|
||||
```
|
||||
|
||||
### Function Documentation
|
||||
|
||||
Always document:
|
||||
- Purpose
|
||||
- Parameters
|
||||
- Return values
|
||||
- Exceptions
|
||||
- Examples (when helpful)
|
||||
|
||||
## Git Commit Messages
|
||||
|
||||
Follow conventional commits:
|
||||
|
||||
```
|
||||
type(scope): subject
|
||||
|
||||
body
|
||||
|
||||
footer
|
||||
```
|
||||
|
||||
Types:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation
|
||||
- `test`: Tests
|
||||
- `refactor`: Code refactoring
|
||||
- `chore`: Maintenance
|
||||
|
||||
Example:
|
||||
```
|
||||
feat(trading): add trailing stop order support
|
||||
|
||||
Implemented trailing stop orders with configurable
|
||||
trail percentage and activation price.
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
### What to Review
|
||||
|
||||
- Code correctness
|
||||
- Test coverage
|
||||
- Documentation
|
||||
- Performance
|
||||
- Security
|
||||
- Style compliance
|
||||
|
||||
### Review Checklist
|
||||
|
||||
- [ ] Code follows style guide
|
||||
- [ ] Tests are included
|
||||
- [ ] Documentation is updated
|
||||
- [ ] No security issues
|
||||
- [ ] Performance is acceptable
|
||||
- [ ] Error handling is appropriate
|
||||
|
||||
115
docs/developer/contributing.md
Normal file
115
docs/developer/contributing.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Thank you for your interest in contributing to Crypto Trader!
|
||||
|
||||
## How to Contribute
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
1. Check if the bug has already been reported
|
||||
2. Create a new issue with:
|
||||
- Clear description
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- System information
|
||||
- Log files (if applicable)
|
||||
|
||||
### Suggesting Features
|
||||
|
||||
1. Check if the feature has been suggested
|
||||
2. Create a feature request with:
|
||||
- Use case description
|
||||
- Proposed solution
|
||||
- Benefits
|
||||
- Implementation considerations
|
||||
|
||||
### Code Contributions
|
||||
|
||||
1. **Fork the repository**
|
||||
2. **Create a feature branch**:
|
||||
```bash
|
||||
git checkout -b feature/my-feature
|
||||
```
|
||||
3. **Make your changes**:
|
||||
- Follow coding standards
|
||||
- Write tests
|
||||
- Update documentation
|
||||
4. **Run tests**:
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
5. **Commit changes**:
|
||||
```bash
|
||||
git commit -m "feat(module): add new feature"
|
||||
```
|
||||
6. **Push to your fork**:
|
||||
```bash
|
||||
git push origin feature/my-feature
|
||||
```
|
||||
7. **Create a pull request**
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### Before Submitting
|
||||
|
||||
- [ ] Code follows style guide
|
||||
- [ ] Tests are included and passing
|
||||
- [ ] Documentation is updated
|
||||
- [ ] No new warnings or errors
|
||||
- [ ] Coverage is maintained
|
||||
|
||||
### Pull Request Template
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
Brief description of changes
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation update
|
||||
|
||||
## Testing
|
||||
How was this tested?
|
||||
|
||||
## Checklist
|
||||
- [ ] Code follows style guide
|
||||
- [ ] Tests added/updated
|
||||
- [ ] Documentation updated
|
||||
- [ ] No breaking changes (or documented)
|
||||
```
|
||||
|
||||
## Code Review
|
||||
|
||||
All pull requests require review:
|
||||
|
||||
- At least one approval required
|
||||
- Address review comments
|
||||
- Keep PR focused and small when possible
|
||||
- Respond to feedback promptly
|
||||
|
||||
## Development Setup
|
||||
|
||||
See [Development Setup](setup.md) for environment setup.
|
||||
|
||||
## Coding Standards
|
||||
|
||||
See [Coding Standards](coding_standards.md) for detailed guidelines.
|
||||
|
||||
## Questions?
|
||||
|
||||
- Open a discussion
|
||||
- Check existing issues
|
||||
- Review documentation
|
||||
- Ask in comments
|
||||
|
||||
## Recognition
|
||||
|
||||
Contributors will be recognized in:
|
||||
- CONTRIBUTORS.md
|
||||
- Release notes
|
||||
- Project documentation
|
||||
|
||||
Thank you for contributing!
|
||||
|
||||
233
docs/developer/creating_strategies.md
Normal file
233
docs/developer/creating_strategies.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# Creating Custom Strategies
|
||||
|
||||
This guide explains how to create custom trading strategies for Crypto Trader.
|
||||
|
||||
## Strategy Framework
|
||||
|
||||
All strategies inherit from `BaseStrategy` and implement a standardized interface. This allows the trading engine to execute any strategy uniformly.
|
||||
|
||||
## Basic Strategy Structure
|
||||
|
||||
```python
|
||||
from src.strategies.base import BaseStrategy, StrategyRegistry
|
||||
from src.data.indicators import get_indicators
|
||||
import pandas as pd
|
||||
from typing import Dict, Any
|
||||
|
||||
class MyCustomStrategy(BaseStrategy):
|
||||
"""My custom trading strategy."""
|
||||
|
||||
def __init__(self, strategy_id: int, name: str, symbol: str, timeframe: str, parameters: Dict[str, Any]):
|
||||
super().__init__(strategy_id, name, symbol, timeframe, parameters)
|
||||
# Initialize strategy-specific parameters
|
||||
self.my_param = parameters.get("my_param", 10)
|
||||
|
||||
async def on_data(self, new_data: pd.DataFrame):
|
||||
"""Called when new data is available."""
|
||||
# Update internal data
|
||||
self.current_data = pd.concat([self.current_data, new_data])
|
||||
|
||||
# Generate signal if enough data
|
||||
if len(self.current_data) >= self.my_param:
|
||||
signal = await self.generate_signal()
|
||||
if signal["signal"] != "hold":
|
||||
# Signal generated - will be handled by trading engine
|
||||
pass
|
||||
|
||||
async def generate_signal(self) -> Dict[str, Any]:
|
||||
"""Generate trading signal."""
|
||||
# Calculate indicators
|
||||
indicators = get_indicators()
|
||||
rsi = indicators.rsi(self.current_data['close'], period=14)
|
||||
|
||||
# Generate signal based on strategy logic
|
||||
current_rsi = rsi.iloc[-1]
|
||||
current_price = self.current_data['close'].iloc[-1]
|
||||
|
||||
if current_rsi < 30:
|
||||
return {"signal": "buy", "price": current_price}
|
||||
elif current_rsi > 70:
|
||||
return {"signal": "sell", "price": current_price}
|
||||
else:
|
||||
return {"signal": "hold", "price": current_price}
|
||||
|
||||
async def calculate_position_size(self, capital: float, risk_percentage: float) -> float:
|
||||
"""Calculate position size."""
|
||||
return capital * risk_percentage
|
||||
|
||||
# Register strategy
|
||||
StrategyRegistry.register_strategy("my_custom", MyCustomStrategy)
|
||||
```
|
||||
|
||||
## Required Methods
|
||||
|
||||
### `on_data(new_data: pd.DataFrame)`
|
||||
|
||||
Called when new OHLCV data is available for the strategy's timeframe.
|
||||
|
||||
**Responsibilities**:
|
||||
- Update internal data storage
|
||||
- Calculate indicators
|
||||
- Generate signals when conditions are met
|
||||
|
||||
### `generate_signal() -> Dict[str, Any]`
|
||||
|
||||
Analyzes current data and generates a trading signal.
|
||||
|
||||
**Returns**:
|
||||
```python
|
||||
{
|
||||
"signal": "buy" | "sell" | "hold",
|
||||
"price": float, # Optional, current price
|
||||
"confidence": float, # Optional, 0.0 to 1.0
|
||||
}
|
||||
```
|
||||
|
||||
### `calculate_position_size(capital: float, risk_percentage: float) -> float`
|
||||
|
||||
Calculates the appropriate position size based on capital and risk.
|
||||
|
||||
**Parameters**:
|
||||
- `capital`: Available trading capital
|
||||
- `risk_percentage`: Percentage of capital to risk (0.0 to 1.0)
|
||||
|
||||
**Returns**: Position size in base currency
|
||||
|
||||
## Using Technical Indicators
|
||||
|
||||
Access indicators through the indicators library:
|
||||
|
||||
```python
|
||||
from src.data.indicators import get_indicators
|
||||
|
||||
indicators = get_indicators()
|
||||
|
||||
# Calculate indicators
|
||||
sma = indicators.sma(data['close'], period=20)
|
||||
ema = indicators.ema(data['close'], period=20)
|
||||
rsi = indicators.rsi(data['close'], period=14)
|
||||
macd_result = indicators.macd(data['close'], fast=12, slow=26, signal=9)
|
||||
bbands = indicators.bollinger_bands(data['close'], period=20, std_dev=2)
|
||||
```
|
||||
|
||||
## Multi-Timeframe Strategies
|
||||
|
||||
Strategies can use multiple timeframes:
|
||||
|
||||
```python
|
||||
from src.strategies.timeframe_manager import get_timeframe_manager
|
||||
|
||||
timeframe_manager = get_timeframe_manager()
|
||||
|
||||
# Get data from different timeframes
|
||||
primary_data = timeframe_manager.get_data(self.symbol, self.timeframe)
|
||||
higher_tf_data = timeframe_manager.get_data(self.symbol, "1d") # Daily for trend
|
||||
|
||||
# Use higher timeframe for trend confirmation
|
||||
if higher_tf_data is not None and len(higher_tf_data) > 0:
|
||||
daily_trend = higher_tf_data['close'].iloc[-1] > higher_tf_data['close'].iloc[-20]
|
||||
if daily_trend:
|
||||
# Only trade in direction of higher timeframe trend
|
||||
pass
|
||||
```
|
||||
|
||||
## Strategy Parameters
|
||||
|
||||
Define configurable parameters:
|
||||
|
||||
```python
|
||||
def __init__(self, strategy_id: int, name: str, symbol: str, timeframe: str, parameters: Dict[str, Any]):
|
||||
super().__init__(strategy_id, name, symbol, timeframe, parameters)
|
||||
|
||||
# Required parameters with defaults
|
||||
self.rsi_period = parameters.get("rsi_period", 14)
|
||||
self.overbought = parameters.get("overbought", 70)
|
||||
self.oversold = parameters.get("oversold", 30)
|
||||
|
||||
# Validate parameters
|
||||
if not (0 < self.oversold < self.overbought < 100):
|
||||
raise ValueError("Invalid RSI thresholds")
|
||||
```
|
||||
|
||||
## Strategy Registration
|
||||
|
||||
Register your strategy so it can be used:
|
||||
|
||||
```python
|
||||
from src.strategies.base import StrategyRegistry
|
||||
|
||||
StrategyRegistry.register_strategy("my_strategy", MyCustomStrategy)
|
||||
```
|
||||
|
||||
## Strategy Lifecycle
|
||||
|
||||
1. **Initialization**: Strategy is created with parameters
|
||||
2. **Start**: `start()` is called when strategy is activated
|
||||
3. **Data Updates**: `on_data()` is called with new candles
|
||||
4. **Signal Generation**: `generate_signal()` is called when conditions are met
|
||||
5. **Order Execution**: Trading engine executes signals
|
||||
6. **Stop**: `stop()` is called when strategy is deactivated
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Parameter Validation**: Validate all parameters in `__init__`
|
||||
2. **Error Handling**: Handle missing data and calculation errors
|
||||
3. **Logging**: Use strategy logger for important events
|
||||
4. **Testing**: Write unit tests for your strategy
|
||||
5. **Documentation**: Document strategy logic and parameters
|
||||
6. **Backtesting**: Always backtest before live trading
|
||||
|
||||
## Example: Complete Strategy
|
||||
|
||||
See `src/strategies/technical/rsi_strategy.py` for a complete example.
|
||||
|
||||
## Available Strategy Examples
|
||||
|
||||
The codebase includes several advanced strategy implementations that serve as examples:
|
||||
|
||||
### Multi-Indicator Confirmation
|
||||
|
||||
See `src/strategies/technical/confirmed_strategy.py` for an example of combining multiple indicators.
|
||||
|
||||
### Divergence Detection
|
||||
|
||||
See `src/strategies/technical/divergence_strategy.py` for an example of divergence detection using the indicators API.
|
||||
|
||||
### Ensemble Methods
|
||||
|
||||
See `src/strategies/ensemble/consensus_strategy.py` for an example of combining multiple strategies with weighted voting.
|
||||
|
||||
### Trend Filtering
|
||||
|
||||
All strategies can use the optional trend filter method from BaseStrategy:
|
||||
|
||||
```python
|
||||
signal = strategy.apply_trend_filter(signal, ohlcv_data, adx_period=14, min_adx=25.0)
|
||||
```
|
||||
|
||||
## Testing Your Strategy
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from src.strategies.my_strategy import MyCustomStrategy
|
||||
|
||||
def test_strategy_signal_generation():
|
||||
"""Test strategy signal generation."""
|
||||
strategy = MyCustomStrategy(
|
||||
strategy_id=1,
|
||||
name="Test Strategy",
|
||||
symbol="BTC/USD",
|
||||
timeframe="1h",
|
||||
parameters={"rsi_period": 14}
|
||||
)
|
||||
|
||||
# Create test data
|
||||
test_data = pd.DataFrame({
|
||||
'close': [100, 101, 102, 103, 104]
|
||||
})
|
||||
|
||||
# Test signal generation
|
||||
signal = await strategy.generate_signal()
|
||||
assert signal["signal"] in ["buy", "sell", "hold"]
|
||||
```
|
||||
|
||||
270
docs/developer/frontend_testing.md
Normal file
270
docs/developer/frontend_testing.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# Frontend Testing Guide
|
||||
|
||||
This guide explains how to test the React frontend components and pages.
|
||||
|
||||
## Testing Setup
|
||||
|
||||
The frontend uses Vitest (recommended) or Jest for testing React components. To set up testing:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest @vitest/ui jsdom
|
||||
```
|
||||
|
||||
Add to `package.json`:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest --coverage"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Component Testing
|
||||
|
||||
Test individual components in isolation:
|
||||
|
||||
```typescript
|
||||
// frontend/src/components/__tests__/StatusIndicator.test.tsx
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import StatusIndicator from '../StatusIndicator';
|
||||
|
||||
describe('StatusIndicator', () => {
|
||||
it('renders connected status correctly', () => {
|
||||
render(<StatusIndicator status="connected" label="WebSocket" />);
|
||||
expect(screen.getByText('WebSocket')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows correct color for error status', () => {
|
||||
render(<StatusIndicator status="error" label="Error" />);
|
||||
const chip = screen.getByText('Error');
|
||||
expect(chip).toHaveClass('MuiChip-colorError');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Page Testing
|
||||
|
||||
Test complete page workflows:
|
||||
|
||||
```typescript
|
||||
// frontend/src/pages/__tests__/StrategiesPage.test.tsx
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import StrategiesPage from '../StrategiesPage';
|
||||
import * as strategiesApi from '../../api/strategies';
|
||||
|
||||
vi.mock('../../api/strategies');
|
||||
|
||||
describe('StrategiesPage', () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } }
|
||||
});
|
||||
|
||||
it('displays list of strategies', async () => {
|
||||
const mockStrategies = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Strategy',
|
||||
strategy_type: 'rsi',
|
||||
enabled: false,
|
||||
paper_trading: true,
|
||||
// ... other fields
|
||||
}
|
||||
];
|
||||
|
||||
vi.mocked(strategiesApi.strategiesApi.listStrategies).mockResolvedValue(mockStrategies);
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StrategiesPage />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Strategy')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows create strategy button', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StrategiesPage />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
expect(screen.getByText('Create Strategy')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### API Integration Testing
|
||||
|
||||
Test API client functions:
|
||||
|
||||
```typescript
|
||||
// frontend/src/api/__tests__/strategies.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { strategiesApi } from '../strategies';
|
||||
import { apiClient } from '../client';
|
||||
|
||||
vi.mock('../client');
|
||||
|
||||
describe('strategiesApi', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('lists strategies', async () => {
|
||||
const mockStrategies = [{ id: 1, name: 'Test' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockStrategies });
|
||||
|
||||
const result = await strategiesApi.listStrategies();
|
||||
expect(result).toEqual(mockStrategies);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/api/strategies/');
|
||||
});
|
||||
|
||||
it('creates strategy', async () => {
|
||||
const newStrategy = { name: 'New', strategy_type: 'rsi' };
|
||||
const created = { id: 1, ...newStrategy };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: created });
|
||||
|
||||
const result = await strategiesApi.createStrategy(newStrategy);
|
||||
expect(result).toEqual(created);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Hook Testing
|
||||
|
||||
Test custom React hooks:
|
||||
|
||||
```typescript
|
||||
// frontend/src/hooks/__tests__/useWebSocket.test.ts
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { useWebSocket } from '../useWebSocket';
|
||||
|
||||
// Mock WebSocket
|
||||
global.WebSocket = vi.fn(() => ({
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
send: vi.fn(),
|
||||
close: vi.fn(),
|
||||
readyState: WebSocket.OPEN,
|
||||
})) as any;
|
||||
|
||||
describe('useWebSocket', () => {
|
||||
it('connects to WebSocket', async () => {
|
||||
const { result } = renderHook(() => useWebSocket('ws://localhost:8000/ws/'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isConnected).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles messages', async () => {
|
||||
const { result } = renderHook(() => useWebSocket('ws://localhost:8000/ws/'));
|
||||
|
||||
// Simulate message
|
||||
const ws = (global.WebSocket as any).mock.results[0].value;
|
||||
const messageEvent = new MessageEvent('message', {
|
||||
data: JSON.stringify({ type: 'order_update', order_id: 1 })
|
||||
});
|
||||
ws.onmessage(messageEvent);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.lastMessage).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Components to Test
|
||||
|
||||
1. **Pages**:
|
||||
- `StrategiesPage` - CRUD operations, start/stop
|
||||
- `TradingPage` - Order placement, position closing
|
||||
- `DashboardPage` - AutoPilot controls, system health
|
||||
- `PortfolioPage` - Position management, charts
|
||||
- `BacktestPage` - Backtest execution, results
|
||||
- `SettingsPage` - All settings tabs
|
||||
|
||||
2. **Components**:
|
||||
- `StrategyDialog` - Form validation, parameter configuration
|
||||
- `OrderForm` - Order type handling, validation
|
||||
- `PositionCard` - Position display, close functionality
|
||||
- `StatusIndicator` - Status display
|
||||
- `SystemHealth` - Health status aggregation
|
||||
- `DataFreshness` - Timestamp calculations
|
||||
- `OperationsPanel` - Operation display
|
||||
|
||||
3. **Hooks**:
|
||||
- `useWebSocket` - Connection, messages, subscriptions
|
||||
- `useRealtimeData` - Query invalidation
|
||||
|
||||
4. **Contexts**:
|
||||
- `SnackbarContext` - Notification display
|
||||
- `WebSocketProvider` - WebSocket management
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run tests in watch mode
|
||||
npm test -- --watch
|
||||
|
||||
# Run tests with UI
|
||||
npm run test:ui
|
||||
|
||||
# Generate coverage report
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Mock External Dependencies**: Mock API calls, WebSocket connections
|
||||
2. **Test User Interactions**: Use `@testing-library/user-event` for clicks, typing
|
||||
3. **Test Error States**: Verify error handling and display
|
||||
4. **Test Loading States**: Ensure loading indicators appear
|
||||
5. **Test Accessibility**: Use `@testing-library/jest-dom` matchers
|
||||
6. **Keep Tests Fast**: Mock expensive operations
|
||||
7. **Test Edge Cases**: Empty states, error conditions, boundary values
|
||||
|
||||
## Example Test Suite Structure
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── components/
|
||||
│ ├── __tests__/
|
||||
│ │ ├── StatusIndicator.test.tsx
|
||||
│ │ ├── StrategyDialog.test.tsx
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── pages/
|
||||
│ ├── __tests__/
|
||||
│ │ ├── StrategiesPage.test.tsx
|
||||
│ │ ├── TradingPage.test.tsx
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── hooks/
|
||||
│ ├── __tests__/
|
||||
│ │ ├── useWebSocket.test.ts
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
└── api/
|
||||
├── __tests__/
|
||||
│ ├── strategies.test.ts
|
||||
│ └── ...
|
||||
└── ...
|
||||
```
|
||||
|
||||
243
docs/developer/pricing_providers.md
Normal file
243
docs/developer/pricing_providers.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Pricing Provider Development Guide
|
||||
|
||||
This guide explains how to add new pricing data providers to the Crypto Trader system.
|
||||
|
||||
## Overview
|
||||
|
||||
Pricing providers are responsible for fetching market data (prices, OHLCV candlestick data) without requiring API keys. They differ from exchange adapters, which handle trading operations and require authentication.
|
||||
|
||||
The system uses a multi-tier provider strategy:
|
||||
- **Primary Providers**: CCXT-based providers (Kraken, Coinbase, Binance)
|
||||
- **Fallback Provider**: CoinGecko API
|
||||
|
||||
## Provider Interface
|
||||
|
||||
All pricing providers must implement the `BasePricingProvider` interface, located in `src/data/providers/base_provider.py`.
|
||||
|
||||
### Required Methods
|
||||
|
||||
1. **`name` (property)**: Return the provider's display name
|
||||
2. **`supports_websocket` (property)**: Return True if the provider supports WebSocket connections
|
||||
3. **`connect()`**: Establish connection to the provider, return True if successful
|
||||
4. **`disconnect()`**: Close connection and clean up resources
|
||||
5. **`get_ticker(symbol: str) -> Dict`**: Get current ticker data for a symbol
|
||||
6. **`get_ohlcv(symbol, timeframe, since, limit) -> List[List]`**: Get historical OHLCV data
|
||||
7. **`subscribe_ticker(symbol, callback) -> bool`**: Subscribe to real-time ticker updates
|
||||
|
||||
### Ticker Data Format
|
||||
|
||||
The `get_ticker()` method should return a dictionary with the following keys:
|
||||
|
||||
```python
|
||||
{
|
||||
'symbol': str, # Trading pair (e.g., 'BTC/USD')
|
||||
'bid': Decimal, # Best bid price
|
||||
'ask': Decimal, # Best ask price
|
||||
'last': Decimal, # Last traded price
|
||||
'high': Decimal, # 24h high
|
||||
'low': Decimal, # 24h low
|
||||
'volume': Decimal, # 24h volume
|
||||
'timestamp': int, # Unix timestamp in milliseconds
|
||||
}
|
||||
```
|
||||
|
||||
### OHLCV Data Format
|
||||
|
||||
The `get_ohlcv()` method should return a list of candles, where each candle is:
|
||||
|
||||
```python
|
||||
[timestamp_ms, open, high, low, close, volume]
|
||||
```
|
||||
|
||||
All values should be numeric (float or Decimal).
|
||||
|
||||
## Creating a New Provider
|
||||
|
||||
### Step 1: Create Provider Class
|
||||
|
||||
Create a new file in `src/data/providers/` (e.g., `my_provider.py`):
|
||||
|
||||
```python
|
||||
"""My custom pricing provider."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Any, Callable
|
||||
from datetime import datetime
|
||||
from .base_provider import BasePricingProvider
|
||||
from ...core.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MyProvider(BasePricingProvider):
|
||||
"""My custom pricing provider implementation."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "MyProvider"
|
||||
|
||||
@property
|
||||
def supports_websocket(self) -> bool:
|
||||
return False # Set to True if WebSocket supported
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to provider."""
|
||||
try:
|
||||
# Initialize connection
|
||||
self._connected = True
|
||||
logger.info(f"Connected to {self.name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect: {e}")
|
||||
self._connected = False
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from provider."""
|
||||
self._connected = False
|
||||
logger.info(f"Disconnected from {self.name}")
|
||||
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get current ticker data."""
|
||||
# Implementation here
|
||||
pass
|
||||
|
||||
def get_ohlcv(
|
||||
self,
|
||||
symbol: str,
|
||||
timeframe: str = '1h',
|
||||
since: Optional[datetime] = None,
|
||||
limit: int = 100
|
||||
) -> List[List]:
|
||||
"""Get OHLCV data."""
|
||||
# Implementation here
|
||||
pass
|
||||
|
||||
def subscribe_ticker(self, symbol: str, callback: Callable) -> bool:
|
||||
"""Subscribe to ticker updates."""
|
||||
# Implementation here
|
||||
pass
|
||||
```
|
||||
|
||||
### Step 2: Register Provider
|
||||
|
||||
Update `src/data/providers/__init__.py` to include your provider:
|
||||
|
||||
```python
|
||||
from .my_provider import MyProvider
|
||||
|
||||
__all__ = [..., 'MyProvider']
|
||||
```
|
||||
|
||||
### Step 3: Add to Pricing Service
|
||||
|
||||
Update `src/data/pricing_service.py` to include your provider in the initialization:
|
||||
|
||||
```python
|
||||
# Add to _initialize_providers method
|
||||
try:
|
||||
my_provider = MyProvider()
|
||||
if my_provider.connect():
|
||||
self._providers[my_provider.name] = my_provider
|
||||
self._provider_priority.append(my_provider.name)
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing MyProvider: {e}")
|
||||
```
|
||||
|
||||
### Step 4: Add Configuration
|
||||
|
||||
Update `src/core/config.py` to include configuration options for your provider:
|
||||
|
||||
```python
|
||||
"data_providers": {
|
||||
"primary": [
|
||||
# ... existing providers ...
|
||||
{"name": "my_provider", "enabled": True, "priority": 4},
|
||||
],
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Always catch exceptions in provider methods
|
||||
- Return empty data structures (`{}` or `[]`) on error rather than raising
|
||||
- Log errors with appropriate detail level
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
- Respect API rate limits
|
||||
- Implement appropriate delays between requests
|
||||
- Use exponential backoff for retries
|
||||
|
||||
### Symbol Normalization
|
||||
|
||||
- Override `normalize_symbol()` if your provider uses different symbol formats
|
||||
- Handle common variations (BTC/USD vs BTC-USD vs BTCUSD)
|
||||
|
||||
### Caching
|
||||
|
||||
- The pricing service handles caching automatically
|
||||
- Focus on providing fresh data from the API
|
||||
- Don't implement your own caching layer
|
||||
|
||||
### Testing
|
||||
|
||||
Create unit tests in `tests/unit/data/providers/test_my_provider.py`:
|
||||
|
||||
```python
|
||||
"""Unit tests for MyProvider."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from src.data.providers.my_provider import MyProvider
|
||||
|
||||
class TestMyProvider:
|
||||
def test_connect(self):
|
||||
provider = MyProvider()
|
||||
result = provider.connect()
|
||||
assert result is True
|
||||
|
||||
def test_get_ticker(self):
|
||||
provider = MyProvider()
|
||||
provider.connect()
|
||||
ticker = provider.get_ticker("BTC/USD")
|
||||
assert 'last' in ticker
|
||||
assert ticker['last'] > 0
|
||||
```
|
||||
|
||||
## Example: CoinGecko Provider
|
||||
|
||||
See `src/data/providers/coingecko_provider.py` for a complete example of a REST API-based provider.
|
||||
|
||||
## Example: CCXT Provider
|
||||
|
||||
See `src/data/providers/ccxt_provider.py` for an example of a provider that wraps an existing library (CCXT).
|
||||
|
||||
## Health Monitoring
|
||||
|
||||
The pricing service automatically monitors provider health:
|
||||
- Tracks success/failure rates
|
||||
- Measures response times
|
||||
- Implements circuit breaker pattern
|
||||
- Automatically fails over to next provider
|
||||
|
||||
Your provider doesn't need to implement health monitoring - it's handled by the `HealthMonitor` class.
|
||||
|
||||
## Subscriptions
|
||||
|
||||
For real-time updates, implement `subscribe_ticker()`. The service expects:
|
||||
- Subscriptions to be persistent until `unsubscribe_ticker()` is called
|
||||
- Callbacks to be invoked with ticker data dictionaries
|
||||
- Graceful handling of connection failures
|
||||
|
||||
If WebSocket is not supported, use polling with appropriate intervals (typically 1-5 seconds for ticker data).
|
||||
|
||||
## Questions?
|
||||
|
||||
For more information, see:
|
||||
- `src/data/providers/base_provider.py` - Base interface
|
||||
- `src/data/pricing_service.py` - Service implementation
|
||||
- `src/data/health_monitor.py` - Health monitoring
|
||||
180
docs/developer/release_process.md
Normal file
180
docs/developer/release_process.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Release Process
|
||||
|
||||
This guide outlines the process for releasing new versions of Crypto Trader.
|
||||
|
||||
## Version Numbering
|
||||
|
||||
Follow [Semantic Versioning](https://semver.org/):
|
||||
|
||||
- **MAJOR**: Breaking changes
|
||||
- **MINOR**: New features (backward compatible)
|
||||
- **PATCH**: Bug fixes (backward compatible)
|
||||
|
||||
Example: `1.2.3`
|
||||
|
||||
## Release Checklist
|
||||
|
||||
### Pre-Release
|
||||
|
||||
- [ ] All tests passing
|
||||
- [ ] Code coverage meets threshold (95%)
|
||||
- [ ] Documentation is up to date
|
||||
- [ ] Changelog is updated
|
||||
- [ ] Version number updated
|
||||
- [ ] All issues for milestone are closed
|
||||
|
||||
### Release Steps
|
||||
|
||||
1. **Update Version**
|
||||
|
||||
Update version in:
|
||||
- `setup.py`
|
||||
- `docs/api/source/conf.py`
|
||||
- `src/__init__.py` (if exists)
|
||||
|
||||
2. **Update Changelog**
|
||||
|
||||
Document all changes in `CHANGELOG.md`:
|
||||
- Added features
|
||||
- Changed features
|
||||
- Deprecated features
|
||||
- Removed features
|
||||
- Fixed bugs
|
||||
- Security updates
|
||||
|
||||
3. **Create Release Branch**
|
||||
|
||||
```bash
|
||||
git checkout -b release/v1.2.3
|
||||
git push origin release/v1.2.3
|
||||
```
|
||||
|
||||
4. **Build and Test**
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Build AppImage
|
||||
./packaging/build_appimage.sh
|
||||
|
||||
# Test AppImage
|
||||
./crypto_trader-*.AppImage --test
|
||||
```
|
||||
|
||||
5. **Create Release Tag**
|
||||
|
||||
```bash
|
||||
git tag -a v1.2.3 -m "Release v1.2.3"
|
||||
git push origin v1.2.3
|
||||
```
|
||||
|
||||
6. **Create GitHub Release**
|
||||
|
||||
- Create release on GitHub
|
||||
- Upload AppImage
|
||||
- Add release notes from changelog
|
||||
- Mark as latest release
|
||||
|
||||
7. **Merge to Main**
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git merge release/v1.2.3
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## AppImage Release
|
||||
|
||||
### Building AppImage
|
||||
|
||||
```bash
|
||||
cd packaging
|
||||
./build_appimage.sh
|
||||
```
|
||||
|
||||
### AppImage Requirements
|
||||
|
||||
- Must be executable
|
||||
- Must include all dependencies
|
||||
- Must be tested on target system
|
||||
- Must include version in filename
|
||||
|
||||
### AppImage Distribution
|
||||
|
||||
- Upload to GitHub Releases
|
||||
- Include checksums (SHA256)
|
||||
- Provide installation instructions
|
||||
- Test on clean system
|
||||
|
||||
## Post-Release
|
||||
|
||||
1. **Announce Release**
|
||||
|
||||
- Update website (if applicable)
|
||||
- Post release notes
|
||||
- Notify users
|
||||
|
||||
2. **Monitor**
|
||||
|
||||
- Watch for issues
|
||||
- Monitor error reports
|
||||
- Track download statistics
|
||||
|
||||
3. **Hotfixes**
|
||||
|
||||
If critical bugs are found:
|
||||
- Create hotfix branch
|
||||
- Fix and test
|
||||
- Release patch version
|
||||
- Merge to main
|
||||
|
||||
## Release Notes Template
|
||||
|
||||
```markdown
|
||||
# Release v1.2.3
|
||||
|
||||
## Added
|
||||
- New feature X
|
||||
- New feature Y
|
||||
|
||||
## Changed
|
||||
- Improved performance of Z
|
||||
- Updated dependency versions
|
||||
|
||||
## Fixed
|
||||
- Fixed bug in A
|
||||
- Fixed issue with B
|
||||
|
||||
## Security
|
||||
- Security update for C
|
||||
|
||||
## Installation
|
||||
|
||||
Download the AppImage:
|
||||
- [crypto_trader-1.2.3-x86_64.AppImage](link)
|
||||
|
||||
SHA256: [checksum]
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
[Any upgrade instructions]
|
||||
```
|
||||
|
||||
## Emergency Releases
|
||||
|
||||
For critical security or stability issues:
|
||||
|
||||
1. Create hotfix branch from latest release
|
||||
2. Apply fix
|
||||
3. Test thoroughly
|
||||
4. Release immediately
|
||||
5. Merge to main and develop
|
||||
|
||||
## Version History
|
||||
|
||||
Maintain version history in:
|
||||
- `CHANGELOG.md`
|
||||
- GitHub Releases
|
||||
- Documentation
|
||||
|
||||
315
docs/developer/setup.md
Normal file
315
docs/developer/setup.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# Development Environment Setup
|
||||
|
||||
This guide will help you set up a development environment for Crypto Trader.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11 or higher
|
||||
- Node.js 18 or higher
|
||||
- Git
|
||||
- Virtual environment tool (venv or virtualenv)
|
||||
- Code editor (VS Code, PyCharm, etc.)
|
||||
- PostgreSQL 14 or higher
|
||||
- Redis 5.0 or higher
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd crypto_trader
|
||||
```
|
||||
|
||||
### 2. Create Virtual Environment
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 3. Install Python Dependencies
|
||||
|
||||
```bash
|
||||
# Install main dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Install development dependencies
|
||||
pip install -r tests/requirements.txt
|
||||
|
||||
# Install documentation dependencies
|
||||
pip install -r docs/requirements.txt
|
||||
```
|
||||
|
||||
### 4. Install Frontend Dependencies
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
### 5. Install Pre-commit Hooks (Optional)
|
||||
|
||||
```bash
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## Development Tools
|
||||
|
||||
### Recommended IDE Setup
|
||||
|
||||
**VS Code**:
|
||||
- Python extension
|
||||
- Pylance for type checking
|
||||
- Python Test Explorer
|
||||
- ESLint and Prettier for frontend
|
||||
- YAML extension
|
||||
|
||||
**PyCharm**:
|
||||
- Configure Python interpreter
|
||||
- Set up test runner
|
||||
- Configure code style
|
||||
- Enable JavaScript/TypeScript support
|
||||
|
||||
### Code Quality Tools
|
||||
|
||||
```bash
|
||||
# Install linting and formatting tools
|
||||
pip install black flake8 mypy pylint
|
||||
|
||||
# Format code
|
||||
black src/ tests/
|
||||
|
||||
# Lint code
|
||||
flake8 src/ tests/
|
||||
|
||||
# Type checking
|
||||
mypy src/
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
### PostgreSQL (Required)
|
||||
|
||||
You must have a PostgreSQL instance running for development.
|
||||
|
||||
```bash
|
||||
# Install PostgreSQL
|
||||
# Create development database
|
||||
createdb crypto_trader_dev
|
||||
|
||||
# Update config.yaml or set env var
|
||||
# DATABASE_URL=postgresql+asyncpg://user:password@localhost/crypto_trader_dev
|
||||
```
|
||||
|
||||
### SQLite (Internal)
|
||||
|
||||
Used internally for unit tests (in-memory) only. No setup required.
|
||||
|
||||
## Redis Setup
|
||||
|
||||
Redis is required for distributed state management and Celery background tasks (e.g., ML model retraining).
|
||||
|
||||
```bash
|
||||
# Install Redis (Ubuntu/Debian)
|
||||
sudo apt-get install redis-server
|
||||
```
|
||||
|
||||
**Starting Redis**:
|
||||
|
||||
```bash
|
||||
# Option 1: Using system service (requires sudo)
|
||||
sudo service redis-server start
|
||||
|
||||
# Option 2: Direct daemon mode (for containers/restricted environments)
|
||||
redis-server --daemonize yes
|
||||
|
||||
# Verify
|
||||
redis-cli ping # Should return PONG
|
||||
```
|
||||
|
||||
> **Note**: In containerized environments (Toolbox, Distrobox, Docker, etc.) where `sudo` is not available, use the direct daemon mode option. If you see "Connection refused" errors when using features like "Retrain Model", Redis is likely not running.
|
||||
|
||||
### Default Configuration
|
||||
|
||||
Redis defaults to `localhost:6379`. Override in `config.yaml`:
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
host: "127.0.0.1"
|
||||
port: 6379
|
||||
db: 0
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
|
||||
### Start All Services (Recommended)
|
||||
|
||||
Use the helper script to start all services:
|
||||
|
||||
```bash
|
||||
./scripts/start_all.sh
|
||||
```
|
||||
|
||||
### Start Services Manually
|
||||
|
||||
```bash
|
||||
# 1. Start Redis
|
||||
# Use sudo if available, otherwise direct daemon mode
|
||||
sudo service redis-server start # OR: redis-server --daemonize yes
|
||||
|
||||
# 2. Start Celery Worker (background tasks)
|
||||
celery -A src.worker.app worker --loglevel=info &
|
||||
|
||||
# 3. Start Backend API
|
||||
uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000 &
|
||||
|
||||
# 4. Start Frontend
|
||||
cd frontend && npm run dev
|
||||
```
|
||||
|
||||
### Access Points
|
||||
|
||||
- **Frontend**: http://localhost:3000
|
||||
- **Backend API**: http://localhost:8000
|
||||
- **API Docs**: http://localhost:8000/docs
|
||||
|
||||
### Verify Redis/Celery Integration
|
||||
|
||||
```bash
|
||||
python scripts/verify_redis.py
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=src --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/unit/core/test_redis.py
|
||||
|
||||
# Run Redis/Celery tests
|
||||
pytest tests/unit/core/test_redis.py tests/unit/worker/test_tasks.py -v
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
crypto_trader/
|
||||
├── src/ # Backend source code
|
||||
│ ├── autopilot/ # Intelligent autopilot
|
||||
│ ├── core/ # Core utilities (config, redis, logging)
|
||||
│ ├── strategies/ # Trading strategies
|
||||
│ ├── trading/ # Trading engine
|
||||
│ └── worker/ # Celery tasks
|
||||
├── backend/ # FastAPI application
|
||||
│ ├── api/ # API endpoints
|
||||
│ └── main.py # Application entry point
|
||||
├── frontend/ # React frontend
|
||||
│ ├── src/ # React source
|
||||
│ └── package.json # Frontend dependencies
|
||||
├── tests/ # Test suite
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── start_all.sh # Start all services
|
||||
│ └── verify_redis.py # Verify Redis/Celery
|
||||
├── docs/ # Documentation
|
||||
└── requirements.txt # Python dependencies
|
||||
```
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding a New Celery Task
|
||||
|
||||
1. Add task function in `src/worker/tasks.py`
|
||||
2. Configure task routing in `src/worker/app.py` (optional)
|
||||
3. Create API endpoint in `backend/api/`
|
||||
4. Write unit tests in `tests/unit/worker/`
|
||||
|
||||
### Adding a New API Endpoint
|
||||
|
||||
1. Create/update router in `backend/api/`
|
||||
2. Register router in `backend/main.py`
|
||||
3. Add frontend API client in `frontend/src/api/`
|
||||
4. Write tests
|
||||
|
||||
### Running Documentation
|
||||
|
||||
```bash
|
||||
cd docs/api
|
||||
make html
|
||||
# Open build/html/index.html
|
||||
```
|
||||
|
||||
### Building AppImage
|
||||
|
||||
```bash
|
||||
./packaging/build_appimage.sh
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
Set in `config.yaml`:
|
||||
```yaml
|
||||
logging:
|
||||
level: DEBUG
|
||||
```
|
||||
|
||||
### Using Debugger
|
||||
|
||||
```bash
|
||||
# VS Code: Set breakpoints and use debugger
|
||||
# PyCharm: Configure debug configuration
|
||||
# Command line: Use pdb
|
||||
python -m pdb -c continue backend/main.py
|
||||
```
|
||||
|
||||
### Checking Celery Tasks
|
||||
|
||||
```bash
|
||||
# Monitor tasks
|
||||
celery -A src.worker.app events
|
||||
|
||||
# Inspect active tasks
|
||||
celery -A src.worker.app inspect active
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Import errors?**
|
||||
- Verify virtual environment is activated
|
||||
- Check PYTHONPATH
|
||||
- Reinstall dependencies
|
||||
|
||||
**Redis connection failed / "Connection refused" error?**
|
||||
- Ensure Redis is running: `redis-cli ping` (should return `PONG`)
|
||||
- Start Redis if not running:
|
||||
- With sudo: `sudo service redis-server start`
|
||||
- Without sudo: `redis-server --daemonize yes`
|
||||
- Check host/port configuration in `config.yaml`
|
||||
- Verify firewall rules
|
||||
|
||||
**Celery tasks not executing?**
|
||||
- Ensure worker is running: `ps aux | grep celery`
|
||||
- Check worker logs: `tail -f celery.log`
|
||||
- Verify Redis is accessible
|
||||
|
||||
**Tests failing?**
|
||||
- Check test database setup
|
||||
- Verify test fixtures
|
||||
- Review test logs
|
||||
|
||||
**Frontend not connecting?**
|
||||
- Check API is running on port 8000
|
||||
- Verify CORS settings in backend
|
||||
- Check browser console for errors
|
||||
421
docs/developer/testing.md
Normal file
421
docs/developer/testing.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# Testing Guide
|
||||
|
||||
This guide covers testing practices for Crypto Trader, including backend API testing, integration testing, and end-to-end testing.
|
||||
|
||||
## Test Structure
|
||||
|
||||
Tests are organized to mirror the source code structure:
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # Unit tests
|
||||
│ ├── backend/ # Backend API tests
|
||||
│ ├── core/ # Core module tests
|
||||
│ └── ...
|
||||
├── integration/ # Integration tests
|
||||
├── e2e/ # End-to-end tests
|
||||
├── fixtures/ # Test fixtures
|
||||
├── utils/ # Test utilities
|
||||
└── performance/ # Performance benchmarks
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### All Tests
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
### With Coverage
|
||||
|
||||
```bash
|
||||
pytest --cov=src --cov-report=html
|
||||
```
|
||||
|
||||
### Specific Test File
|
||||
|
||||
```bash
|
||||
pytest tests/unit/core/test_config.py
|
||||
```
|
||||
|
||||
### Specific Test
|
||||
|
||||
```bash
|
||||
pytest tests/unit/core/test_config.py::test_config_loading
|
||||
```
|
||||
|
||||
### Verbose Output
|
||||
|
||||
```bash
|
||||
pytest -v
|
||||
```
|
||||
|
||||
### Test Categories
|
||||
|
||||
```bash
|
||||
# Unit tests only
|
||||
pytest -m unit
|
||||
|
||||
# Integration tests only
|
||||
pytest -m integration
|
||||
|
||||
# End-to-end tests only
|
||||
pytest -m e2e
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Test individual functions and classes in isolation:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from src.core.config import get_config
|
||||
|
||||
class TestConfig:
|
||||
"""Tests for configuration system."""
|
||||
|
||||
def test_config_loading(self):
|
||||
"""Test configuration loading."""
|
||||
config = get_config()
|
||||
assert config is not None
|
||||
assert config.config_dir is not None
|
||||
```
|
||||
|
||||
### Backend API Tests
|
||||
|
||||
Test FastAPI endpoints using TestClient:
|
||||
|
||||
```python
|
||||
from fastapi.testclient import TestClient
|
||||
from backend.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def test_get_orders():
|
||||
"""Test getting orders endpoint."""
|
||||
response = client.get("/api/trading/orders")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
def test_place_order():
|
||||
"""Test placing an order."""
|
||||
order_data = {
|
||||
"exchange_id": 1,
|
||||
"symbol": "BTC/USD",
|
||||
"side": "buy",
|
||||
"type": "market",
|
||||
"quantity": 0.1,
|
||||
"paper_trading": True
|
||||
}
|
||||
response = client.post("/api/trading/orders", json=order_data)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["symbol"] == "BTC/USD"
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Test component interactions:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from backend.main import app
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_trading_workflow(client: TestClient):
|
||||
"""Test complete trading workflow."""
|
||||
# Place order
|
||||
order_response = client.post("/api/trading/orders", json={...})
|
||||
assert order_response.status_code == 201
|
||||
order_id = order_response.json()["id"]
|
||||
|
||||
# Check order status
|
||||
status_response = client.get(f"/api/trading/orders/{order_id}")
|
||||
assert status_response.status_code == 200
|
||||
|
||||
# Check portfolio update
|
||||
portfolio_response = client.get("/api/portfolio/current")
|
||||
assert portfolio_response.status_code == 200
|
||||
```
|
||||
|
||||
### End-to-End Tests
|
||||
|
||||
Test complete user workflows:
|
||||
|
||||
```python
|
||||
@pytest.mark.e2e
|
||||
async def test_paper_trading_scenario():
|
||||
"""Test complete paper trading scenario."""
|
||||
# Test full application flow through API
|
||||
pass
|
||||
```
|
||||
|
||||
## Test Fixtures
|
||||
|
||||
Use fixtures for common setup:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from backend.main import app
|
||||
from src.core.database import get_database
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""Test client fixture."""
|
||||
return TestClient(app)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_exchange():
|
||||
"""Mock exchange adapter."""
|
||||
exchange = Mock()
|
||||
exchange.fetch_balance.return_value = {'USD': {'free': 1000}}
|
||||
return exchange
|
||||
|
||||
@pytest.fixture
|
||||
def test_db():
|
||||
"""Test database fixture."""
|
||||
# Use in-memory SQLite for unit tests (fast, isolated)
|
||||
# Note: Requires aiosqlite installed as test dependency
|
||||
db = get_database()
|
||||
# Setup test data
|
||||
yield db
|
||||
# Cleanup
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
### Mocking External APIs
|
||||
|
||||
```python
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
@patch('src.exchanges.coinbase.ccxt')
|
||||
async def test_coinbase_connection(mock_ccxt):
|
||||
"""Test Coinbase connection."""
|
||||
mock_exchange = AsyncMock()
|
||||
mock_ccxt.coinbaseadvanced.return_value = mock_exchange
|
||||
mock_exchange.load_markets = AsyncMock()
|
||||
|
||||
adapter = CoinbaseExchange(...)
|
||||
await adapter.connect()
|
||||
|
||||
assert adapter.is_connected
|
||||
```
|
||||
|
||||
### Mocking Database
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def test_db():
|
||||
"""Create test database."""
|
||||
# Use in-memory SQLite for unit tests (internal use only)
|
||||
from src.core.database import Database
|
||||
db = Database("sqlite:///:memory:")
|
||||
db.create_tables()
|
||||
return db
|
||||
```
|
||||
|
||||
### Mocking Services
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def mock_trading_engine():
|
||||
"""Mock trading engine."""
|
||||
engine = Mock()
|
||||
engine.execute_order.return_value = MockOrder(id=1, status="filled")
|
||||
return engine
|
||||
|
||||
def test_place_order_endpoint(mock_trading_engine):
|
||||
"""Test order placement with mocked engine."""
|
||||
with patch('backend.api.trading.get_trading_engine', return_value=mock_trading_engine):
|
||||
response = client.post("/api/trading/orders", json={...})
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
## Async Testing
|
||||
|
||||
Use `pytest-asyncio` for async tests:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_function():
|
||||
"""Test async function."""
|
||||
result = await my_async_function()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
## WebSocket Testing
|
||||
|
||||
Test WebSocket endpoints:
|
||||
|
||||
```python
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
def test_websocket_connection(client: TestClient):
|
||||
"""Test WebSocket connection."""
|
||||
with client.websocket_connect("/ws/") as websocket:
|
||||
# Send message
|
||||
websocket.send_json({"type": "subscribe", "channel": "prices"})
|
||||
|
||||
# Receive message
|
||||
data = websocket.receive_json()
|
||||
assert data["type"] == "price_update"
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
Target 95% code coverage:
|
||||
|
||||
```bash
|
||||
# Generate coverage report
|
||||
pytest --cov=src --cov-report=html
|
||||
|
||||
# View in browser
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
### Coverage Configuration
|
||||
|
||||
Configure in `pytest.ini`:
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
cov = src
|
||||
cov-report = term-missing
|
||||
cov-report = html
|
||||
cov-fail-under = 95
|
||||
```
|
||||
|
||||
## Frontend Testing
|
||||
|
||||
The frontend uses React Testing Library and Vitest for testing. See [Frontend Testing Guide](./frontend_testing.md) for detailed information.
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest jsdom
|
||||
npm test
|
||||
```
|
||||
|
||||
### Component Testing
|
||||
|
||||
```typescript
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import StrategiesPage from '../pages/StrategiesPage';
|
||||
import * as strategiesApi from '../api/strategies';
|
||||
|
||||
vi.mock('../api/strategies');
|
||||
|
||||
describe('StrategiesPage', () => {
|
||||
it('renders and displays strategies', async () => {
|
||||
const mockStrategies = [{ id: 1, name: 'Test Strategy', ... }];
|
||||
vi.mocked(strategiesApi.strategiesApi.listStrategies).mockResolvedValue(mockStrategies);
|
||||
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StrategiesPage />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Strategy')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Testing New Components
|
||||
|
||||
All new components should have corresponding test files:
|
||||
- `StrategiesPage` → `StrategiesPage.test.tsx`
|
||||
- `TradingPage` → `TradingPage.test.tsx`
|
||||
- `StrategyDialog` → `StrategyDialog.test.tsx`
|
||||
- `OrderForm` → `OrderForm.test.tsx`
|
||||
- And all other new components
|
||||
|
||||
See [Frontend Testing Guide](./frontend_testing.md) for comprehensive testing patterns.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Test Independence**: Tests should not depend on each other
|
||||
2. **Fast Tests**: Unit tests should run quickly (< 1 second each)
|
||||
3. **Clear Names**: Test names should describe what they test
|
||||
4. **One Assertion**: Prefer one assertion per test when possible
|
||||
5. **Mock External**: Mock external dependencies (APIs, databases)
|
||||
6. **Test Edge Cases**: Test boundary conditions and errors
|
||||
7. **Documentation**: Document complex test scenarios
|
||||
8. **Arrange-Act-Assert**: Structure tests clearly
|
||||
9. **Use Fixtures**: Reuse common setup code
|
||||
10. **Isolation**: Each test should be able to run independently
|
||||
|
||||
## Test Organization
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Test single function/class
|
||||
- Mock external dependencies
|
||||
- Fast execution
|
||||
- High coverage
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Test component interactions
|
||||
- Use test database
|
||||
- Test real workflows
|
||||
- Moderate speed
|
||||
|
||||
### E2E Tests
|
||||
|
||||
- Test complete user flows
|
||||
- Use test environment
|
||||
- Slow execution
|
||||
- Critical paths only
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
Tests run automatically in CI/CD:
|
||||
|
||||
- All tests must pass
|
||||
- Coverage must meet threshold (95%)
|
||||
- Code style must pass
|
||||
- Type checking must pass (if using mypy)
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Verbose Output
|
||||
|
||||
```bash
|
||||
pytest -vv # Very verbose
|
||||
pytest -s # Show print statements
|
||||
```
|
||||
|
||||
### Debugging Failed Tests
|
||||
|
||||
```bash
|
||||
# Drop into debugger on failure
|
||||
pytest --pdb
|
||||
|
||||
# Drop into debugger on first failure
|
||||
pytest -x --pdb
|
||||
```
|
||||
|
||||
### Running Last Failed Tests
|
||||
|
||||
```bash
|
||||
pytest --lf # Last failed
|
||||
pytest --ff # Failed first
|
||||
```
|
||||
346
docs/developer/ui_development.md
Normal file
346
docs/developer/ui_development.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Frontend Development Guide
|
||||
|
||||
This guide explains how to develop and extend frontend components in Crypto Trader.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **React 18** - UI framework
|
||||
- **TypeScript** - Type safety
|
||||
- **Material-UI (MUI)** - Component library
|
||||
- **React Query** - Data fetching and caching
|
||||
- **React Router** - Routing
|
||||
- **Vite** - Build tool
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── pages/ # Page components
|
||||
│ ├── components/ # Reusable components
|
||||
│ ├── api/ # API client functions
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── types/ # TypeScript types
|
||||
│ └── App.tsx # Main app component
|
||||
├── public/ # Static assets
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Access at: http://localhost:3000
|
||||
|
||||
## Creating a New Page
|
||||
|
||||
1. Create page component in `src/pages/`:
|
||||
|
||||
```typescript
|
||||
// src/pages/MyPage.tsx
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
export function MyPage() {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4">My Page</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
2. Add route in `src/App.tsx`:
|
||||
|
||||
```typescript
|
||||
import { MyPage } from './pages/MyPage';
|
||||
|
||||
<Route path="/my-page" element={<MyPage />} />
|
||||
```
|
||||
|
||||
## API Integration
|
||||
|
||||
### Creating API Functions
|
||||
|
||||
```typescript
|
||||
// src/api/myService.ts
|
||||
import { apiClient } from './client';
|
||||
|
||||
export const myServiceApi = {
|
||||
async getData() {
|
||||
const response = await apiClient.get('/api/my-service/data');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async createItem(data: CreateItemDto) {
|
||||
const response = await apiClient.post('/api/my-service/items', data);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Using React Query
|
||||
|
||||
```typescript
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { myServiceApi } from '@/api/myService';
|
||||
|
||||
function MyComponent() {
|
||||
// Fetch data
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['myData'],
|
||||
queryFn: myServiceApi.getData
|
||||
});
|
||||
|
||||
// Mutate data
|
||||
const mutation = useMutation({
|
||||
mutationFn: myServiceApi.createItem,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['myData'] });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <Loading />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
<button onClick={() => mutation.mutate(formData)}>Create</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Reusable Components
|
||||
|
||||
```typescript
|
||||
// src/components/DataTable.tsx
|
||||
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
interface DataTableProps<T> {
|
||||
data: T[];
|
||||
columns: Column<T>[];
|
||||
}
|
||||
|
||||
export function DataTable<T>({ data, columns }: DataTableProps<T>) {
|
||||
return (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map(col => (
|
||||
<TableCell key={col.key}>{col.header}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map((row, idx) => (
|
||||
<TableRow key={idx}>
|
||||
{columns.map(col => (
|
||||
<TableCell key={col.key}>{col.render(row)}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Hooks
|
||||
|
||||
```typescript
|
||||
// src/hooks/useOrders.ts
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { tradingApi } from '@/api/trading';
|
||||
|
||||
export function useOrders() {
|
||||
const { data: orders, isLoading, error } = useQuery({
|
||||
queryKey: ['orders'],
|
||||
queryFn: () => tradingApi.getOrders()
|
||||
});
|
||||
|
||||
const placeOrder = useMutation({
|
||||
mutationFn: tradingApi.placeOrder,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['orders'] });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
orders: orders ?? [],
|
||||
isLoading,
|
||||
error,
|
||||
placeOrder: placeOrder.mutate
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## WebSocket Integration
|
||||
|
||||
The application uses an enhanced WebSocket system with message type handling:
|
||||
|
||||
```typescript
|
||||
// Using the WebSocket context
|
||||
import { useWebSocketContext } from '@/components/WebSocketProvider';
|
||||
import { useRealtimeData } from '@/hooks/useRealtimeData';
|
||||
|
||||
function MyComponent() {
|
||||
const { isConnected, lastMessage, subscribe } = useWebSocketContext();
|
||||
|
||||
// Subscribe to specific message types
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribe('order_update', (message) => {
|
||||
// Handle order update
|
||||
console.log('Order updated:', message);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [subscribe]);
|
||||
|
||||
// Or use the real-time data hook for automatic query invalidation
|
||||
useRealtimeData(); // Automatically handles common message types
|
||||
}
|
||||
```
|
||||
|
||||
### Message Types
|
||||
|
||||
The WebSocket supports these message types:
|
||||
- `order_update`: Order status changes
|
||||
- `position_update`: Position changes
|
||||
- `price_update`: Price updates
|
||||
- `alert_triggered`: Alert notifications
|
||||
- `strategy_signal`: Strategy signal notifications
|
||||
- `system_event`: System events and errors
|
||||
|
||||
## Form Handling
|
||||
|
||||
```typescript
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const schema = yup.object({
|
||||
symbol: yup.string().required(),
|
||||
quantity: yup.number().positive().required()
|
||||
});
|
||||
|
||||
function OrderForm() {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
await tradingApi.placeOrder(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<TextField
|
||||
{...register('symbol')}
|
||||
error={!!errors.symbol}
|
||||
helperText={errors.symbol?.message}
|
||||
/>
|
||||
<Button type="submit">Place Order</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The application uses a centralized Snackbar context for error handling:
|
||||
|
||||
```typescript
|
||||
import { useSnackbar } from '@/contexts/SnackbarContext';
|
||||
import ErrorDisplay from '@/components/ErrorDisplay';
|
||||
|
||||
function MyComponent() {
|
||||
const { showError, showSuccess, showWarning, showInfo } = useSnackbar();
|
||||
|
||||
const handleAction = async () => {
|
||||
try {
|
||||
await apiCall();
|
||||
showSuccess('Action completed successfully');
|
||||
} catch (err) {
|
||||
showError(err instanceof Error ? err.message : 'An error occurred');
|
||||
}
|
||||
};
|
||||
|
||||
// For complex error display with retry
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
<ErrorDisplay
|
||||
error={error}
|
||||
title="Operation Failed"
|
||||
onRetry={handleAction}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Available Components
|
||||
|
||||
- `ErrorDisplay`: Enhanced error display with retry functionality
|
||||
- `LoadingSkeleton`: Loading placeholders for tables, cards, lists
|
||||
- `ProgressOverlay`: Overlay with progress indicator
|
||||
- `StatusIndicator`: Connection status indicators
|
||||
- `DataFreshness`: Data freshness timestamps
|
||||
- `HelpTooltip`: Contextual help tooltips
|
||||
- `InfoCard`: Collapsible information cards
|
||||
- `OperationsPanel`: Panel showing running operations
|
||||
|
||||
## Styling
|
||||
|
||||
### Material-UI Theming
|
||||
|
||||
```typescript
|
||||
// src/theme.ts
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#1976d2'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Component Styling
|
||||
|
||||
Use Material-UI's `sx` prop for inline styles:
|
||||
|
||||
```typescript
|
||||
<Box
|
||||
sx={{
|
||||
padding: 2,
|
||||
backgroundColor: 'background.paper',
|
||||
borderRadius: 1
|
||||
}}
|
||||
>
|
||||
Content
|
||||
</Box>
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
See [Testing Guide](./testing.md) for frontend testing strategies.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Type Safety**: Always use TypeScript types
|
||||
2. **Component Composition**: Prefer composition over inheritance
|
||||
3. **Custom Hooks**: Extract reusable logic into hooks
|
||||
4. **Error Handling**: Handle errors gracefully with user feedback
|
||||
5. **Loading States**: Always show loading indicators
|
||||
6. **Accessibility**: Use semantic HTML and ARIA labels
|
||||
7. **Performance**: Use React.memo, useMemo, useCallback appropriately
|
||||
8. **Code Splitting**: Use lazy loading for large components
|
||||
9. **Form Validation**: Validate on both client and server
|
||||
10. **Consistent Patterns**: Follow established patterns in the codebase
|
||||
Reference in New Issue
Block a user