Local changes: Updated model training, removed debug instrumentation, and configuration improvements

This commit is contained in:
kfox
2025-12-26 01:15:43 -05:00
commit cc60da49e7
388 changed files with 57127 additions and 0 deletions

40
docs/developer/README.md Normal file
View 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

View 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.

View 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

View 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!

View 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"]
```

View 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
│ └── ...
└── ...
```

View 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

View 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
View 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
View 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
```

View 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