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

32
.coveragerc Normal file
View File

@@ -0,0 +1,32 @@
[run]
source = src
omit =
*/tests/*
*/__pycache__/*
*/venv/*
*/site-packages/*
*/migrations/*
setup.py
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
if TYPE_CHECKING:
@abstractmethod
@property
@staticmethod
@classmethod
pass
... # ellipsis
precision = 2
show_missing = True
skip_covered = False
[html]
directory = htmlcov

39
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Documentation
on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'src/**'
workflow_dispatch:
jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r docs/requirements.txt
- name: Build documentation
run: |
cd docs/api
make html
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/api/build/html

41
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ['3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r tests/requirements.txt
- name: Run tests
run: |
pytest --cov=src --cov-report=xml --cov-report=term
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella

69
.gitignore vendored Normal file
View File

@@ -0,0 +1,69 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Environment
.env
.env.local
# Database
*.db
*.sqlite
*.sqlite3
# Logs
*.log
logs/
# Data
data/historical/*
!data/historical/.gitkeep
# AppImage
*.AppImage
# OS
.DS_Store
Thumbs.db
# Testing
.pytest_cache/
.coverage
.coverage.*
coverage.xml
htmlcov/
# Packaging
packaging/AppDir/
*.AppImage

51
Dockerfile Normal file
View File

@@ -0,0 +1,51 @@
# Multi-stage build for Crypto Trader
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
# Copy frontend files
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Python backend
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Install FastAPI and uvicorn
RUN pip install --no-cache-dir fastapi uvicorn[standard] python-multipart
# Copy backend code
COPY backend/ ./backend/
COPY src/ ./src/
COPY config/ ./config/
# Copy built frontend from builder
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
# Create data directory
RUN mkdir -p /app/data
# Expose port
EXPOSE 8000
# Set environment variables
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
# Run the application
WORKDIR /app
CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]

344
README.md Normal file
View File

@@ -0,0 +1,344 @@
# Cryptocurrency Trading Platform
[![Python](https://img.shields.io/badge/Python-3.11+-blue.svg)](https://www.python.org/)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
A comprehensive cryptocurrency trading platform with multi-exchange support, real-time trading, backtesting, advanced risk management, and portfolio analytics. Built with a modern web architecture (React frontend + FastAPI backend) while preserving the core Python trading engine.
## Features
- **Modern Web UI**: React + TypeScript + Material-UI with comprehensive feature coverage
- Strategy Management: Full CRUD operations with parameter configuration
- Manual Trading: Order placement, management, and position closing
- Dashboard: AutoPilot controls, system health monitoring, real-time updates
- Portfolio: Allocation charts, position management, performance analytics
- Backtesting: Historical strategy testing with progress tracking
- Settings: Exchange management, alerts, alert history, risk configuration
- **RESTful API**: FastAPI with auto-generated documentation
- **Real-Time Updates**: WebSocket integration for live order, position, and price updates
- **Intelligent Autopilot**: ML-based trading automation
- ML strategy selection with LightGBM/XGBoost ensemble models
- Configurable training: historical days, timeframe, symbols
- Background model training via Celery with real-time progress
- Auto-reload of trained models without restart
- Pre-flight order validation (prevents failed orders)
- Smart order types: LIMIT for better entries, MARKET for urgency
- Stop-loss vs take-profit detection for optimal exit strategy
- Multi-symbol support with independent autopilot instances
- **Multi-Tier Pricing Data**: Robust pricing data system with automatic failover
- Primary providers: CCXT-based (Kraken, Coinbase, Binance) with automatic failover
- Fallback provider: CoinGecko API (free tier, no API keys required)
- Smart caching with configurable TTL
- Health monitoring with circuit breaker pattern
- Works without exchange integrations for paper trading, ML, and backtesting
- **Multi-Exchange Support**: Trade on multiple exchanges (starting with Coinbase)
- **Paper Trading**: Test strategies with virtual funds ($100 default, configurable)
- Configurable fee exchange model (Coinbase, Kraken, Binance)
- Realistic fee simulation with maker/taker rates
- Immediate order execution (no pending orders)
- **Advanced Backtesting**: Realistic backtesting with slippage, fees, and order book simulation
- **Strategy Framework**: Multi-timeframe strategies with scheduling and optimization
- **Risk Management**: Stop-loss, position sizing (Kelly Criterion), drawdown limits, daily loss limits
- **Portfolio Analytics**: Advanced metrics (Sharpe ratio, Sortino ratio, drawdown analysis)
- **Alert System**: Price, indicator, risk, and system alerts with history tracking
- **Export & Reporting**: CSV, PDF, and tax reporting (FIFO/LIFO/specific identification)
- **Futures & Leverage**: Support for futures trading and leverage
- **Fully Local**: No telemetry, all data stored locally with encryption
- **Transparent Operations**: System health indicators, data freshness tracking, operation visibility
- **Background Task Processing**: Celery-powered ML training and report generation
- **Distributed State Management**: Redis-backed autopilot state persistence across restarts
## Quick Start
### Docker (Recommended)
```bash
# Build and run
docker-compose up --build
# Access application
open http://localhost:8000
```
### Development
**Prerequisites**:
- Python 3.11+
- Node.js 18+
- PostgreSQL 14+
- Redis 5.0+
**Quick Setup**:
```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install frontend dependencies
cd frontend && npm install && cd ..
# Start all services (Redis, Celery, Backend, Frontend)
./scripts/start_all.sh
```
**Manual Setup**:
```bash
# 1. Start Redis (choose one option)
sudo service redis-server start # With sudo
# OR: redis-server --daemonize yes # Without sudo (for containers)
# 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 frontend at: http://localhost:3000
API docs at: http://localhost:8000/docs
**Verify Setup**:
```bash
python scripts/verify_redis.py
```
## Architecture
```
Frontend (React) → FastAPI → Python Services → Database
```
- **Frontend**: React + TypeScript + Material-UI
- **Backend**: FastAPI (Python)
- **Services**: Existing Python code (trading engine, strategies, etc.)
- **Database**: PostgreSQL
## Project Structure
```
crypto_trader/
├── backend/ # FastAPI application
│ ├── api/ # API endpoints
│ └── core/ # Dependencies, schemas
├── frontend/ # React application
│ └── src/
│ ├── pages/ # Page components (Dashboard, Strategies, Trading, Portfolio, Backtesting, Settings)
│ ├── components/ # Reusable components (StatusIndicator, SystemHealth, etc.)
│ ├── api/ # API client functions
│ ├── hooks/ # Custom React hooks (useWebSocket, useRealtimeData)
│ ├── contexts/ # React contexts (SnackbarContext)
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Utility functions
├── src/ # Core Python code
│ ├── trading/ # Trading engine
│ ├── strategies/ # Strategy framework
│ ├── portfolio/ # Portfolio tracker
│ ├── backtesting/ # Backtesting engine
│ └── ...
├── tests/ # Test suite
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests (including frontend API workflows)
│ └── e2e/ # End-to-end tests
├── docs/ # Documentation
│ ├── user_manual/ # User documentation
│ ├── developer/ # Developer documentation
│ └── architecture/ # Architecture documentation
└── config/ # Configuration files
```
## API Endpoints
- **Trading**: `/api/trading/*` - Orders, positions, balance
- **Portfolio**: `/api/portfolio/*` - Portfolio data and history
- **Strategies**: `/api/strategies/*` - Strategy management
- **Backtesting**: `/api/backtesting/*` - Run backtests
- **Exchanges**: `/api/exchanges/*` - Exchange management
- **Autopilot**: `/api/autopilot/*` - Intelligent autopilot
- **Market Data**: `/api/market-data/*` - Market data endpoints
- **WebSocket**: `/ws/` - Real-time updates
Full API documentation: http://localhost:8000/docs
## Configuration
Configuration files are stored in `~/.config/crypto_trader/` following XDG Base Directory Specification.
Data is stored in `~/.local/share/crypto_trader/`:
- `trading.db` - Legacy SQLite database (removed)
- `historical/` - Historical market data
- `logs/` - Application logs
See [Configuration Guide](docs/user_manual/configuration.md) for details.
## Environment Variables
Create `.env` file (optional):
```env
VITE_API_URL=http://localhost:8000
VITE_WS_URL=ws://localhost:8000/ws/
DATABASE_URL=postgresql+asyncpg://user:password@localhost/dbname
```
## Requirements
- **Python**: 3.11 or higher
- **Node.js**: 18+ (for frontend)
- **PostgreSQL**: 14+ (required for database)
- **Redis**: 5.0+ (required for state management)
- **Celery**: Included in requirements.txt (for background tasks)
- **Docker**: Optional, for containerized deployment
## Features in Detail
### Trading
- Market and limit orders
- Advanced order types (stop-loss, take-profit, trailing stop, OCO, iceberg)
- Real-time position tracking
- Paper trading simulator
- Futures and leverage trading
### Strategies
- Pre-built strategies:
- **Technical**: RSI, MACD, Moving Average, Bollinger Mean Reversion
- **Composite**: Confirmed Strategy, Divergence Strategy
- **Accumulation**: DCA (Dollar Cost Averaging), Grid Trading
- **Advanced**: Momentum, Consensus (multi-strategy voting)
- **Quantitative**: Statistical Arbitrage (Pairs Trading), Volatility Breakout
- **Alternative Data**: Sentiment/News Trading (with Fear & Greed Index)
- **Market Making**: Bid/Ask spread capture with inventory management
- Custom strategy development
- Multi-timeframe support
- Strategy scheduling with real-time status tracking
- Parameter optimization (grid search, genetic, Bayesian)
- **See [Pairs Trading Guide](docs/guides/pairs_trading_setup.md)** for advanced strategy configuration
### Backtesting
- Historical data replay
- Realistic simulation (slippage, fees)
- Performance metrics (Sharpe, Sortino, drawdown)
- Parameter optimization
- Export results
### Risk Management
- Position sizing (fixed, Kelly Criterion, volatility-based)
- Stop-loss orders
- Maximum drawdown limits
- Daily loss limits
- Portfolio allocation limits
### Portfolio Management
- Real-time P&L tracking
- Advanced analytics
- Portfolio rebalancing
- Performance charts
- Export and reporting
### Pricing Data Providers
- **Multi-Tier Architecture**: Primary CCXT providers with CoinGecko fallback
- **Automatic Failover**: Seamless switching between providers on failure
- **Health Monitoring**: Real-time provider status with circuit breakers
- **Smart Caching**: Configurable TTL-based caching for performance
- **No API Keys Required**: Works with public data APIs for paper trading
- **WebSocket Support**: Real-time price updates via WebSocket
- **Provider Configuration**: Full UI for managing provider priorities and settings
## Documentation
- **[User Manual](docs/user_manual/README.md)** - Complete user guide
- **[Developer Guide](docs/developer/README.md)** - Development documentation
- **[API Documentation](http://localhost:8000/docs)** - Interactive API docs
- **[Architecture Docs](docs/architecture/overview.md)** - System architecture
- **[Deployment Guide](docs/deployment/README.md)** - Deployment instructions
### Quick Links
- [Getting Started](docs/user_manual/getting_started.md)
- [Trading Guide](docs/user_manual/trading.md)
- [Strategy Development](docs/user_manual/strategies.md)
- [Pairs Trading Guide](docs/guides/pairs_trading_setup.md)
- [Backtesting Guide](docs/user_manual/backtesting.md)
- [Configuration](docs/user_manual/configuration.md)
- [Troubleshooting](docs/user_manual/troubleshooting.md)
- [FAQ](docs/user_manual/faq.md)
## Testing
Run the test suite:
```bash
# Install test dependencies
pip install -r tests/requirements.txt
# Run all tests
pytest
# Run with coverage
pytest --cov=src --cov-report=html
# Run specific test category
pytest -m unit
pytest -m integration
pytest -m e2e
```
## Docker Deployment
```bash
# Build
docker build -t crypto-trader:latest .
# Run
docker run -d \
-p 8000:8000 \
-v $(pwd)/data:/app/data \
-v $(pwd)/config:/app/config \
crypto-trader:latest
```
## Contributing
We welcome contributions! See [Contributing Guidelines](docs/developer/contributing.md) for details.
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Write/update tests
5. Submit a pull request
## Development
See [Developer Guide](docs/developer/README.md) for:
- Development environment setup
- Coding standards
- Testing guidelines
- Architecture overview
- Adding new features
## License
MIT License
## Support
- **Documentation**: See [docs/](docs/) directory
- **API Docs**: http://localhost:8000/docs (when running)
- **Issues**: Report bugs and request features via GitHub Issues
- **FAQ**: Check [FAQ](docs/user_manual/faq.md) for common questions
## Migration from PyQt6
The application has been migrated from PyQt6 desktop app to web architecture while preserving 90% of existing Python code. See [Migration Guide](docs/migration_guide.md) for details.

47
backend/README.md Normal file
View File

@@ -0,0 +1,47 @@
# Crypto Trader Backend API
FastAPI backend for the Crypto Trader application.
## Setup
```bash
pip install -r requirements.txt
pip install -r backend/requirements.txt
```
## Development
```bash
python -m uvicorn backend.main:app --reload --port 8000
```
Access API docs at: http://localhost:8000/docs
## API Endpoints
- **Trading**: `/api/trading/*`
- **Portfolio**: `/api/portfolio/*`
- **Strategies**: `/api/strategies/*`
- **Backtesting**: `/api/backtesting/*`
- **Exchanges**: `/api/exchanges/*`
- **WebSocket**: `/ws/`
## Project Structure
```
backend/
├── api/ # API route handlers
├── core/ # Core utilities (dependencies, schemas)
└── main.py # FastAPI application
```
## Dependencies
The backend uses existing Python code from `src/`:
- Trading engine
- Strategy framework
- Portfolio tracker
- Backtesting engine
- All other services
These are imported via `sys.path` modification in `main.py`.

1
backend/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Backend API package."""

1
backend/api/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""API routers package."""

144
backend/api/alerts.py Normal file
View File

@@ -0,0 +1,144 @@
"""Alerts API endpoints."""
from typing import List, Optional
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from datetime import datetime
from sqlalchemy import select
from src.core.database import Alert, get_database
router = APIRouter()
def get_alert_manager():
"""Get alert manager instance."""
from src.alerts.manager import get_alert_manager as _get_alert_manager
return _get_alert_manager()
class AlertCreate(BaseModel):
"""Create alert request."""
name: str
alert_type: str # price, indicator, risk, system
condition: dict
class AlertUpdate(BaseModel):
"""Update alert request."""
name: Optional[str] = None
condition: Optional[dict] = None
enabled: Optional[bool] = None
class AlertResponse(BaseModel):
"""Alert response."""
id: int
name: str
alert_type: str
condition: dict
enabled: bool
triggered: bool
triggered_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
@router.get("/", response_model=List[AlertResponse])
async def list_alerts(
enabled_only: bool = False,
manager=Depends(get_alert_manager)
):
"""List all alerts."""
try:
alerts = await manager.list_alerts(enabled_only=enabled_only)
return [AlertResponse.model_validate(alert) for alert in alerts]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/", response_model=AlertResponse)
async def create_alert(
alert_data: AlertCreate,
manager=Depends(get_alert_manager)
):
"""Create a new alert."""
try:
alert = await manager.create_alert(
name=alert_data.name,
alert_type=alert_data.alert_type,
condition=alert_data.condition
)
return AlertResponse.model_validate(alert)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{alert_id}", response_model=AlertResponse)
async def get_alert(alert_id: int):
"""Get alert by ID."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Alert).where(Alert.id == alert_id)
result = await session.execute(stmt)
alert = result.scalar_one_or_none()
if not alert:
raise HTTPException(status_code=404, detail="Alert not found")
return AlertResponse.model_validate(alert)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{alert_id}", response_model=AlertResponse)
async def update_alert(alert_id: int, alert_data: AlertUpdate):
"""Update an alert."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Alert).where(Alert.id == alert_id)
result = await session.execute(stmt)
alert = result.scalar_one_or_none()
if not alert:
raise HTTPException(status_code=404, detail="Alert not found")
if alert_data.name is not None:
alert.name = alert_data.name
if alert_data.condition is not None:
alert.condition = alert_data.condition
if alert_data.enabled is not None:
alert.enabled = alert_data.enabled
await session.commit()
await session.refresh(alert)
return AlertResponse.model_validate(alert)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{alert_id}")
async def delete_alert(alert_id: int):
"""Delete an alert."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Alert).where(Alert.id == alert_id)
result = await session.execute(stmt)
alert = result.scalar_one_or_none()
if not alert:
raise HTTPException(status_code=404, detail="Alert not found")
await session.delete(alert)
await session.commit()
return {"status": "deleted", "alert_id": alert_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

564
backend/api/autopilot.py Normal file
View File

@@ -0,0 +1,564 @@
"""AutoPilot API endpoints."""
from typing import Dict, Any, Optional, List
from fastapi import APIRouter, HTTPException, BackgroundTasks
from pydantic import BaseModel
from ..core.dependencies import get_database
from ..core.schemas import OrderSide
from src.core.database import get_database as get_db
# Import autopilot - path should be set up in main.py
from src.autopilot import (
stop_all_autopilots,
get_intelligent_autopilot,
get_strategy_selector,
get_performance_tracker,
get_performance_tracker,
get_autopilot_mode_info,
)
from src.worker.tasks import train_model_task
from src.core.config import get_config
from celery.result import AsyncResult
router = APIRouter()
class BootstrapConfig(BaseModel):
"""Bootstrap training data configuration."""
days: int = 90
timeframe: str = "1h"
min_samples_per_strategy: int = 10
symbols: List[str] = ["BTC/USD", "ETH/USD"]
class MultiSymbolAutopilotConfig(BaseModel):
"""Multi-symbol autopilot configuration."""
symbols: List[str]
mode: str = "intelligent"
auto_execute: bool = False
timeframe: str = "1h"
exchange_id: int = 1
paper_trading: bool = True
interval: float = 60.0
# =============================================================================
# Intelligent Autopilot Endpoints
# =============================================================================
class IntelligentAutopilotConfig(BaseModel):
symbol: str
exchange_id: int = 1
timeframe: str = "1h"
interval: float = 60.0
paper_trading: bool = True
# =============================================================================
# Unified Autopilot Endpoints
# =============================================================================
class UnifiedAutopilotConfig(BaseModel):
"""Unified autopilot configuration (Inteligent Mode)."""
symbol: str
mode: str = "intelligent" # Kept for compatibility but only "intelligent" is supported
auto_execute: bool = False
interval: float = 60.0
exchange_id: int = 1
timeframe: str = "1h"
paper_trading: bool = True
@router.post("/intelligent/start", deprecated=True)
async def start_intelligent_autopilot(
config: IntelligentAutopilotConfig,
background_tasks: BackgroundTasks
):
"""Start the Intelligent Autopilot engine.
.. deprecated:: Use /start-unified instead with mode='intelligent'
"""
try:
autopilot = get_intelligent_autopilot(
symbol=config.symbol,
exchange_id=config.exchange_id,
timeframe=config.timeframe,
interval=config.interval,
paper_trading=config.paper_trading
)
if not autopilot.is_running:
background_tasks.add_task(autopilot.start)
return {
"status": "started",
"symbol": config.symbol,
"timeframe": config.timeframe
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/intelligent/stop")
async def stop_intelligent_autopilot(symbol: str, timeframe: str = "1h"):
"""Stop the Intelligent Autopilot engine."""
try:
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
autopilot.stop()
return {"status": "stopped", "symbol": symbol}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/intelligent/status/{symbol:path}")
async def get_intelligent_status(symbol: str, timeframe: str = "1h"):
"""Get Intelligent Autopilot status."""
try:
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
return autopilot.get_status()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/intelligent/performance")
async def get_intelligent_performance(
strategy_name: Optional[str] = None,
days: int = 30
):
"""Get strategy performance metrics."""
try:
tracker = get_performance_tracker()
if strategy_name:
metrics = tracker.calculate_metrics(strategy_name, period_days=days)
return {"strategy": strategy_name, "metrics": metrics}
else:
# Get all strategies
history = tracker.get_performance_history(days=days)
if history.empty:
return {"strategies": []}
strategies = history['strategy_name'].unique()
results = {}
for strat in strategies:
results[strat] = tracker.calculate_metrics(strat, period_days=days)
return {"strategies": results}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/intelligent/training-stats")
async def get_training_stats(days: int = 365):
"""Get statistics about available training data.
Returns:
Dictionary with total samples and per-strategy counts
"""
try:
tracker = get_performance_tracker()
counts = await tracker.get_strategy_sample_counts(days=days)
return {
"total_samples": sum(counts.values()),
"strategy_counts": counts
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/intelligent/retrain")
async def retrain_model(force: bool = False, bootstrap: bool = True):
"""Manually trigger model retraining (Background Task).
Offloads training to Celery worker.
"""
try:
# Get all bootstrap config to pass to worker
config = get_config()
symbols = config.get("autopilot.intelligent.bootstrap.symbols", ["BTC/USD", "ETH/USD"])
days = config.get("autopilot.intelligent.bootstrap.days", 90)
timeframe = config.get("autopilot.intelligent.bootstrap.timeframe", "1h")
min_samples = config.get("autopilot.intelligent.bootstrap.min_samples_per_strategy", 10)
# Submit to Celery with all configured parameters
task = train_model_task.delay(
force_retrain=force,
bootstrap=bootstrap,
symbols=symbols,
days=days,
timeframe=timeframe,
min_samples_per_strategy=min_samples
)
return {
"status": "queued",
"message": "Model retraining started in background",
"task_id": task.id
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/intelligent/model-info")
async def get_model_info():
"""Get ML model information."""
try:
selector = get_strategy_selector()
return selector.get_model_info()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/intelligent/reset")
async def reset_model():
"""Reset/delete all saved ML models and training data.
This clears all persisted model files AND training data from database,
allowing for a fresh start with new features.
"""
try:
from pathlib import Path
from src.core.database import get_database, MarketConditionsSnapshot, StrategyPerformance
from sqlalchemy import delete
# Get model directory
model_dir = Path.home() / ".local" / "share" / "crypto_trader" / "models"
deleted_count = 0
if model_dir.exists():
# Delete all strategy selector model files
for model_file in model_dir.glob("strategy_selector_*.joblib"):
model_file.unlink()
deleted_count += 1
# Clear training data from database
db = get_database()
db_cleared = 0
try:
async with db.get_session() as session:
# Delete all market conditions snapshots
result1 = await session.execute(delete(MarketConditionsSnapshot))
# Delete all strategy performance records
result2 = await session.execute(delete(StrategyPerformance))
await session.commit()
db_cleared = result1.rowcount + result2.rowcount
except Exception as e:
# Database clearing is optional - continue even if it fails
pass
# Reset the in-memory model state
selector = get_strategy_selector()
from src.autopilot.models import StrategySelectorModel
selector.model = StrategySelectorModel(model_type="classifier")
return {
"status": "success",
"message": f"Deleted {deleted_count} model file(s) and {db_cleared} training records. Model reset to untrained state.",
"deleted_count": deleted_count,
"db_records_cleared": db_cleared
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# =============================================================================
# Multi-Symbol Autopilot Endpoints
# =============================================================================
@router.post("/multi-symbol/start")
async def start_multi_symbol_autopilot(
config: MultiSymbolAutopilotConfig,
background_tasks: BackgroundTasks
):
"""Start autopilot for multiple symbols simultaneously.
Args:
config: Multi-symbol autopilot configuration
background_tasks: FastAPI background tasks
"""
try:
results = []
for symbol in config.symbols:
# Always use intelligent mode
autopilot = get_intelligent_autopilot(
symbol=symbol,
exchange_id=config.exchange_id,
timeframe=config.timeframe,
interval=config.interval,
paper_trading=config.paper_trading
)
autopilot.enable_auto_execution = config.auto_execute
if not autopilot.is_running:
# Set running flag synchronously before scheduling background task
autopilot._running = True
background_tasks.add_task(autopilot.start)
results.append({"symbol": symbol, "status": "started"})
else:
results.append({"symbol": symbol, "status": "already_running"})
return {
"status": "success",
"mode": "intelligent",
"symbols": results
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/multi-symbol/stop")
async def stop_multi_symbol_autopilot(
symbols: List[str],
mode: str = "intelligent",
timeframe: str = "1h"
):
"""Stop autopilot for multiple symbols.
Args:
symbols: List of symbols to stop
mode: Autopilot mode (pattern or intelligent)
timeframe: Timeframe for intelligent mode
"""
try:
results = []
for symbol in symbols:
# Always use intelligent mode
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
autopilot.stop()
results.append({"symbol": symbol, "status": "stopped"})
return {
"status": "success",
"symbols": results
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/multi-symbol/status")
async def get_multi_symbol_status(
symbols: str = "", # Comma-separated list
mode: str = "intelligent",
timeframe: str = "1h"
):
"""Get status for multiple symbols.
Args:
symbols: Comma-separated list of symbols (empty = all running)
mode: Autopilot mode
timeframe: Timeframe for intelligent mode
"""
from src.autopilot.intelligent_autopilot import _intelligent_autopilots
try:
results = []
if symbols:
symbol_list = [s.strip() for s in symbols.split(",")]
else:
# Get all running autopilots (intelligent only)
symbol_list = [key.split(":")[0] for key in _intelligent_autopilots.keys()]
for symbol in symbol_list:
try:
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
status = autopilot.get_status()
status["symbol"] = symbol
status["mode"] = "intelligent"
results.append(status)
except Exception:
results.append({
"symbol": symbol,
"mode": "intelligent",
"running": False,
"error": "Not found"
})
return {
"mode": mode,
"symbols": results,
"total_running": sum(1 for r in results if r.get("running", False))
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# =============================================================================
# Unified Autopilot Endpoints (New)
# =============================================================================
@router.get("/modes")
async def get_autopilot_modes():
"""Get information about available autopilot modes.
Returns mode descriptions, capabilities, tradeoffs, and comparison data.
"""
try:
return get_autopilot_mode_info()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/start-unified")
async def start_unified_autopilot(
config: UnifiedAutopilotConfig,
background_tasks: BackgroundTasks
):
"""Start autopilot with unified interface (Intelligent Mode only)."""
try:
# Validate mode (for backward compatibility of API clients sending mode)
if config.mode and config.mode != "intelligent":
# We allow it but will treat it as intelligent if possible, or raise error if critical
pass
# Start ML-based autopilot
autopilot = get_intelligent_autopilot(
symbol=config.symbol,
exchange_id=config.exchange_id,
timeframe=config.timeframe,
interval=config.interval,
paper_trading=config.paper_trading
)
# Set auto-execution if enabled
if config.auto_execute:
autopilot.enable_auto_execution = True
if not autopilot.is_running:
# Schedule background task (state management handled by autopilot.start via Redis)
background_tasks.add_task(autopilot.start)
return {
"status": "started",
"mode": "intelligent",
"symbol": config.symbol,
"timeframe": config.timeframe,
"auto_execute": config.auto_execute
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/stop-unified")
async def stop_unified_autopilot(symbol: str, mode: str, timeframe: str = "1h"):
"""Stop autopilot for a symbol."""
try:
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
autopilot.stop()
return {"status": "stopped", "symbol": symbol, "mode": "intelligent"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/status-unified/{symbol:path}")
async def get_unified_status(symbol: str, mode: str, timeframe: str = "1h"):
"""Get autopilot status for a symbol."""
try:
autopilot = get_intelligent_autopilot(symbol=symbol, timeframe=timeframe)
# Use distributed status check (Redis)
status = await autopilot.get_distributed_status()
status["mode"] = "intelligent"
return status
except Exception as e:
logger.error(f"Error getting unified status: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/bootstrap-config", response_model=BootstrapConfig)
async def get_bootstrap_config():
"""Get bootstrap training data configuration."""
from src.core.config import get_config
config = get_config()
return BootstrapConfig(
days=config.get("autopilot.intelligent.bootstrap.days", 90),
timeframe=config.get("autopilot.intelligent.bootstrap.timeframe", "1h"),
min_samples_per_strategy=config.get("autopilot.intelligent.bootstrap.min_samples_per_strategy", 10),
symbols=config.get("autopilot.intelligent.bootstrap.symbols", ["BTC/USD", "ETH/USD"]),
)
@router.put("/bootstrap-config")
async def update_bootstrap_config(settings: BootstrapConfig):
"""Update bootstrap training data configuration."""
from src.core.config import get_config
config = get_config()
try:
config.set("autopilot.intelligent.bootstrap.days", settings.days)
config.set("autopilot.intelligent.bootstrap.timeframe", settings.timeframe)
config.set("autopilot.intelligent.bootstrap.min_samples_per_strategy", settings.min_samples_per_strategy)
config.set("autopilot.intelligent.bootstrap.symbols", settings.symbols)
# Also update the strategy selector instance if it exists
selector = get_strategy_selector()
selector.bootstrap_days = settings.days
selector.bootstrap_timeframe = settings.timeframe
selector.min_samples_per_strategy = settings.min_samples_per_strategy
selector.bootstrap_symbols = settings.symbols
return {"status": "success", "message": "Bootstrap configuration updated"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/tasks/{task_id}")
async def get_task_status(task_id: str):
"""Get status of a background task."""
try:
task_result = AsyncResult(task_id)
try:
# Accessing status or result might raise an exception if deserialization fails
status = task_result.status
result_data = task_result.result if task_result.ready() else None
meta_data = task_result.info if status == 'PROGRESS' else None
# serialized exception handling
if isinstance(result_data, Exception):
result_data = {
"error": str(result_data),
"type": type(result_data).__name__,
"detail": str(result_data)
}
elif status == "FAILURE" and (not result_data or result_data == {}):
# If failure but empty result, try to get traceback or use a default message
tb = getattr(task_result, 'traceback', None)
if tb:
result_data = {"error": "Task failed", "detail": str(tb)}
else:
result_data = {"error": "Task failed with no error info", "detail": "Check worker logs for details"}
result = {
"task_id": task_id,
"status": status,
"result": result_data
}
if meta_data:
result["meta"] = meta_data
return result
except Exception as inner_e:
# If Celery fails to get status/result (e.g. serialization error), return FAILURE
# This prevents 500 errors in the API when the task itself failed badly
return {
"task_id": task_id,
"status": "FAILURE",
"result": {"error": str(inner_e), "detail": "Failed to retrieve task status"},
"meta": {"error": "Task retrieval failed"}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -0,0 +1,78 @@
"""Backtesting API endpoints."""
from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks
from typing import Dict, Any
from sqlalchemy import select
import uuid
from ..core.dependencies import get_backtesting_engine, get_strategy_registry
from ..core.schemas import BacktestRequest, BacktestResponse
from src.core.database import Strategy, get_database
router = APIRouter()
# Store running backtests
_backtests: Dict[str, Dict[str, Any]] = {}
@router.post("/run", response_model=BacktestResponse)
async def run_backtest(
backtest_data: BacktestRequest,
background_tasks: BackgroundTasks,
backtest_engine=Depends(get_backtesting_engine)
):
"""Run a backtest."""
try:
db = get_database()
async with db.get_session() as session:
# Get strategy
stmt = select(Strategy).where(Strategy.id == backtest_data.strategy_id)
result = await session.execute(stmt)
strategy_db = result.scalar_one_or_none()
if not strategy_db:
raise HTTPException(status_code=404, detail="Strategy not found")
# Create strategy instance
registry = get_strategy_registry()
strategy_instance = registry.create_instance(
strategy_id=strategy_db.id,
name=strategy_db.class_name,
parameters=strategy_db.parameters,
timeframes=strategy_db.timeframes or [backtest_data.timeframe]
)
if not strategy_instance:
raise HTTPException(status_code=400, detail="Failed to create strategy instance")
# Run backtest
results = backtest_engine.run_backtest(
strategy=strategy_instance,
symbol=backtest_data.symbol,
exchange=backtest_data.exchange,
timeframe=backtest_data.timeframe,
start_date=backtest_data.start_date,
end_date=backtest_data.end_date,
initial_capital=backtest_data.initial_capital,
slippage=backtest_data.slippage,
fee_rate=backtest_data.fee_rate
)
if "error" in results:
raise HTTPException(status_code=400, detail=results["error"])
return BacktestResponse(
results=results,
status="completed"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/results/{backtest_id}")
async def get_backtest_results(backtest_id: str):
"""Get backtest results by ID."""
if backtest_id not in _backtests:
raise HTTPException(status_code=404, detail="Backtest not found")
return _backtests[backtest_id]

42
backend/api/exchanges.py Normal file
View File

@@ -0,0 +1,42 @@
"""Exchange API endpoints."""
from typing import List
from fastapi import APIRouter, HTTPException
from sqlalchemy import select
from ..core.schemas import ExchangeResponse
from src.core.database import Exchange, get_database
router = APIRouter()
@router.get("/", response_model=List[ExchangeResponse])
async def list_exchanges():
"""List all exchanges."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).order_by(Exchange.name)
result = await session.execute(stmt)
exchanges = result.scalars().all()
return [ExchangeResponse.model_validate(e) for e in exchanges]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{exchange_id}", response_model=ExchangeResponse)
async def get_exchange(exchange_id: int):
"""Get exchange by ID."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange = result.scalar_one_or_none()
if not exchange:
raise HTTPException(status_code=404, detail="Exchange not found")
return ExchangeResponse.model_validate(exchange)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

280
backend/api/market_data.py Normal file
View File

@@ -0,0 +1,280 @@
"""Market Data API endpoints."""
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from fastapi import APIRouter, HTTPException, Query, Body
from pydantic import BaseModel
import pandas as pd
from src.core.database import MarketData, get_database
from src.data.pricing_service import get_pricing_service
from src.core.config import get_config
router = APIRouter()
@router.get("/ohlcv/{symbol:path}")
async def get_ohlcv(
symbol: str,
timeframe: str = "1h",
limit: int = 100,
exchange: str = "coinbase" # Default exchange
):
"""Get OHLCV data for a symbol."""
from sqlalchemy import select
try:
# Try database first
try:
db = get_database()
async with db.get_session() as session:
# Use select() for async compatibility
stmt = select(MarketData).filter_by(
symbol=symbol,
timeframe=timeframe,
exchange=exchange
).order_by(MarketData.timestamp.desc()).limit(limit)
result = await session.execute(stmt)
data = result.scalars().all()
if data:
return [
{
"time": int(d.timestamp.timestamp()),
"open": float(d.open),
"high": float(d.high),
"low": float(d.low),
"close": float(d.close),
"volume": float(d.volume)
}
for d in reversed(data)
]
except Exception as db_error:
import sys
print(f"Database query failed, falling back to live data: {db_error}", file=sys.stderr)
# If no data in DB or DB error, fetch live from pricing service
try:
pricing_service = get_pricing_service()
# pricing_service.get_ohlcv is currently sync in its implementation but we call it from our async handler
ohlcv_data = pricing_service.get_ohlcv(
symbol=symbol,
timeframe=timeframe,
limit=limit
)
if ohlcv_data:
# Convert to frontend format: [timestamp, open, high, low, close, volume] -> {time, open, high, low, close, volume}
return [
{
"time": int(candle[0] / 1000), # Convert ms to seconds
"open": float(candle[1]),
"high": float(candle[2]),
"low": float(candle[3]),
"close": float(candle[4]),
"volume": float(candle[5])
}
for candle in ohlcv_data
]
except Exception as fetch_error:
import sys
print(f"Failed to fetch live data: {fetch_error}", file=sys.stderr)
# If all else fails, return empty list
return []
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/ticker/{symbol:path}")
async def get_ticker(symbol: str):
"""Get current ticker data for a symbol.
Returns ticker data with provider information.
"""
try:
pricing_service = get_pricing_service()
ticker_data = pricing_service.get_ticker(symbol)
if not ticker_data:
raise HTTPException(status_code=404, detail=f"Ticker data not available for {symbol}")
active_provider = pricing_service.get_active_provider()
return {
"symbol": symbol,
"bid": float(ticker_data.get('bid', 0)),
"ask": float(ticker_data.get('ask', 0)),
"last": float(ticker_data.get('last', 0)),
"high": float(ticker_data.get('high', 0)),
"low": float(ticker_data.get('low', 0)),
"volume": float(ticker_data.get('volume', 0)),
"timestamp": ticker_data.get('timestamp'),
"provider": active_provider,
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/providers/health")
async def get_provider_health(provider: Optional[str] = Query(None, description="Specific provider name")):
"""Get health status for pricing providers.
Args:
provider: Optional provider name to get health for specific provider
"""
try:
pricing_service = get_pricing_service()
health_data = pricing_service.get_provider_health(provider)
return {
"active_provider": pricing_service.get_active_provider(),
"health": health_data,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/providers/status")
async def get_provider_status():
"""Get detailed status for all pricing providers."""
try:
pricing_service = get_pricing_service()
health_data = pricing_service.get_provider_health()
cache_stats = pricing_service.get_cache_stats()
return {
"active_provider": pricing_service.get_active_provider(),
"providers": health_data,
"cache": cache_stats,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/providers/config")
async def get_provider_config():
"""Get provider configuration."""
try:
config = get_config()
provider_config = config.get("data_providers", {})
return provider_config
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
class ProviderConfigUpdate(BaseModel):
"""Provider configuration update model."""
primary: Optional[List[Dict[str, Any]]] = None
fallback: Optional[Dict[str, Any]] = None
caching: Optional[Dict[str, Any]] = None
websocket: Optional[Dict[str, Any]] = None
@router.put("/providers/config")
async def update_provider_config(config_update: ProviderConfigUpdate = Body(...)):
"""Update provider configuration."""
try:
config = get_config()
current_config = config.get("data_providers", {})
# Update configuration
if config_update.primary is not None:
current_config["primary"] = config_update.primary
if config_update.fallback is not None:
current_config["fallback"] = {**current_config.get("fallback", {}), **config_update.fallback}
if config_update.caching is not None:
current_config["caching"] = {**current_config.get("caching", {}), **config_update.caching}
if config_update.websocket is not None:
current_config["websocket"] = {**current_config.get("websocket", {}), **config_update.websocket}
# Save configuration
config.set("data_providers", current_config)
config.save()
return {"message": "Configuration updated successfully", "config": current_config}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/spread")
async def get_spread_data(
primary_symbol: str = Query(..., description="Primary symbol (e.g., SOL/USD)"),
secondary_symbol: str = Query(..., description="Secondary symbol (e.g., AVAX/USD)"),
timeframe: str = Query("1h", description="Timeframe"),
lookback: int = Query(50, description="Number of candles to fetch"),
):
"""Get spread and Z-Score data for pairs trading visualization.
Returns spread ratio and Z-Score time series for the given symbol pair.
"""
try:
pricing_service = get_pricing_service()
# Fetch OHLCV for both symbols
ohlcv_a = pricing_service.get_ohlcv(
symbol=primary_symbol,
timeframe=timeframe,
limit=lookback
)
ohlcv_b = pricing_service.get_ohlcv(
symbol=secondary_symbol,
timeframe=timeframe,
limit=lookback
)
if not ohlcv_a or not ohlcv_b:
raise HTTPException(status_code=404, detail="Could not fetch data for one or both symbols")
# Convert to DataFrames
df_a = pd.DataFrame(ohlcv_a, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df_b = pd.DataFrame(ohlcv_b, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
# Align by length
min_len = min(len(df_a), len(df_b))
df_a = df_a.tail(min_len).reset_index(drop=True)
df_b = df_b.tail(min_len).reset_index(drop=True)
# Calculate spread (ratio)
closes_a = df_a['close'].astype(float)
closes_b = df_b['close'].astype(float)
spread = closes_a / closes_b
# Calculate Z-Score with rolling window
lookback_window = min(20, min_len - 1)
rolling_mean = spread.rolling(window=lookback_window).mean()
rolling_std = spread.rolling(window=lookback_window).std()
z_score = (spread - rolling_mean) / rolling_std
# Build response
result = []
for i in range(min_len):
result.append({
"timestamp": int(df_a['timestamp'].iloc[i]),
"spread": float(spread.iloc[i]) if not pd.isna(spread.iloc[i]) else None,
"zScore": float(z_score.iloc[i]) if not pd.isna(z_score.iloc[i]) else None,
"priceA": float(closes_a.iloc[i]),
"priceB": float(closes_b.iloc[i]),
})
# Filter out entries with null Z-Score (during warmup period)
result = [r for r in result if r["zScore"] is not None]
return {
"primarySymbol": primary_symbol,
"secondarySymbol": secondary_symbol,
"timeframe": timeframe,
"lookbackWindow": lookback_window,
"data": result,
"currentSpread": result[-1]["spread"] if result else None,
"currentZScore": result[-1]["zScore"] if result else None,
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

84
backend/api/portfolio.py Normal file
View File

@@ -0,0 +1,84 @@
"""Portfolio API endpoints."""
from typing import Optional
from fastapi import APIRouter, HTTPException, Depends, Query
from datetime import datetime, timedelta
from ..core.dependencies import get_portfolio_tracker
from ..core.schemas import PortfolioResponse, PortfolioHistoryResponse
router = APIRouter()
# Import portfolio analytics
def get_portfolio_analytics():
"""Get portfolio analytics instance."""
from src.portfolio.analytics import get_portfolio_analytics as _get_analytics
return _get_analytics()
@router.get("/current", response_model=PortfolioResponse)
async def get_current_portfolio(
paper_trading: bool = True,
tracker=Depends(get_portfolio_tracker)
):
"""Get current portfolio state."""
try:
portfolio = await tracker.get_current_portfolio(paper_trading=paper_trading)
return PortfolioResponse(**portfolio)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/history", response_model=PortfolioHistoryResponse)
async def get_portfolio_history(
days: int = Query(30, ge=1, le=365),
paper_trading: bool = True,
tracker=Depends(get_portfolio_tracker)
):
"""Get portfolio history."""
try:
history = await tracker.get_portfolio_history(days=days, paper_trading=paper_trading)
dates = [h['timestamp'] if isinstance(h['timestamp'], str) else h['timestamp'].isoformat()
for h in history]
values = [float(h['total_value']) for h in history]
pnl = [float(h.get('total_pnl', 0)) for h in history]
return PortfolioHistoryResponse(
dates=dates,
values=values,
pnl=pnl
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/positions/update-prices")
async def update_positions_prices(
prices: dict,
paper_trading: bool = True,
tracker=Depends(get_portfolio_tracker)
):
"""Update current prices for positions."""
try:
from decimal import Decimal
price_dict = {k: Decimal(str(v)) for k, v in prices.items()}
await tracker.update_positions_prices(price_dict, paper_trading=paper_trading)
return {"status": "updated"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/risk-metrics")
async def get_risk_metrics(
days: int = Query(30, ge=1, le=365),
paper_trading: bool = True,
analytics=Depends(get_portfolio_analytics)
):
"""Get portfolio risk metrics."""
try:
metrics = await analytics.get_performance_metrics(days=days, paper_trading=paper_trading)
return metrics
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

272
backend/api/reporting.py Normal file
View File

@@ -0,0 +1,272 @@
"""Reporting API endpoints for CSV and PDF export."""
from fastapi import APIRouter, HTTPException, Query, Body
from fastapi.responses import StreamingResponse
from typing import Optional, Dict, Any
from datetime import datetime
from sqlalchemy import select
import io
import csv
import tempfile
from pathlib import Path
from src.core.database import Trade, get_database
router = APIRouter()
def get_csv_exporter():
"""Get CSV exporter instance."""
from src.reporting.csv_exporter import get_csv_exporter as _get_csv_exporter
return _get_csv_exporter()
def get_pdf_generator():
"""Get PDF generator instance."""
from src.reporting.pdf_generator import get_pdf_generator as _get_pdf_generator
return _get_pdf_generator()
@router.post("/backtest/csv")
async def export_backtest_csv(
results: Dict[str, Any] = Body(...),
):
"""Export backtest results as CSV."""
try:
output = io.StringIO()
writer = csv.writer(output)
# Write header
writer.writerow(['Metric', 'Value'])
# Write metrics
writer.writerow(['Total Return', f"{(results.get('total_return', 0) * 100):.2f}%"])
writer.writerow(['Sharpe Ratio', f"{results.get('sharpe_ratio', 0):.2f}"])
writer.writerow(['Sortino Ratio', f"{results.get('sortino_ratio', 0):.2f}"])
writer.writerow(['Max Drawdown', f"{(results.get('max_drawdown', 0) * 100):.2f}%"])
writer.writerow(['Win Rate', f"{(results.get('win_rate', 0) * 100):.2f}%"])
writer.writerow(['Total Trades', results.get('total_trades', 0)])
writer.writerow(['Final Value', f"${results.get('final_value', 0):.2f}"])
writer.writerow(['Initial Capital', f"${results.get('initial_capital', 0):.2f}"])
# Write trades if available
if results.get('trades'):
writer.writerow([])
writer.writerow(['Trades'])
writer.writerow(['Timestamp', 'Side', 'Price', 'Quantity', 'Value'])
for trade in results['trades']:
writer.writerow([
trade.get('timestamp', ''),
trade.get('side', ''),
f"${trade.get('price', 0):.2f}",
trade.get('quantity', 0),
f"${(trade.get('price', 0) * trade.get('quantity', 0)):.2f}",
])
output.seek(0)
return StreamingResponse(
iter([output.getvalue()]),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=backtest_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/backtest/pdf")
async def export_backtest_pdf(
results: Dict[str, Any] = Body(...),
):
"""Export backtest results as PDF."""
try:
pdf_generator = get_pdf_generator()
# Create temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
tmp_path = Path(tmp_file.name)
# Convert results to metrics format expected by PDF generator
metrics = {
'total_return_percent': (results.get('total_return', 0) * 100),
'sharpe_ratio': results.get('sharpe_ratio', 0),
'sortino_ratio': results.get('sortino_ratio', 0),
'max_drawdown': results.get('max_drawdown', 0),
'win_rate': results.get('win_rate', 0),
}
# Generate PDF
success = pdf_generator.generate_performance_report(
tmp_path,
metrics,
"Backtest Report"
)
if not success:
raise HTTPException(status_code=500, detail="Failed to generate PDF")
# Read PDF and return as stream
with open(tmp_path, 'rb') as f:
pdf_content = f.read()
# Clean up
tmp_path.unlink()
return StreamingResponse(
io.BytesIO(pdf_content),
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename=backtest_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/trades/csv")
async def export_trades_csv(
start_date: Optional[str] = None,
end_date: Optional[str] = None,
paper_trading: bool = True,
):
"""Export trades as CSV."""
try:
csv_exporter = get_csv_exporter()
# Parse dates if provided
start = datetime.fromisoformat(start_date) if start_date else None
end = datetime.fromisoformat(end_date) if end_date else None
# Create temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp_file:
tmp_path = Path(tmp_file.name)
# Export to file
success = csv_exporter.export_trades(
filepath=tmp_path,
paper_trading=paper_trading,
start_date=start,
end_date=end
)
if not success:
raise HTTPException(status_code=500, detail="Failed to export trades")
# Read and return
with open(tmp_path, 'r') as f:
csv_content = f.read()
tmp_path.unlink()
return StreamingResponse(
iter([csv_content]),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=trades_{datetime.now().strftime('%Y%m%d')}.csv"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/portfolio/csv")
async def export_portfolio_csv(
paper_trading: bool = True,
):
"""Export portfolio as CSV."""
try:
csv_exporter = get_csv_exporter()
# Create temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp_file:
tmp_path = Path(tmp_file.name)
# Export to file
success = csv_exporter.export_portfolio(filepath=tmp_path)
if not success:
raise HTTPException(status_code=500, detail="Failed to export portfolio")
# Read and return
with open(tmp_path, 'r') as f:
csv_content = f.read()
tmp_path.unlink()
return StreamingResponse(
iter([csv_content]),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=portfolio_{datetime.now().strftime('%Y%m%d')}.csv"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/tax/{method}")
async def generate_tax_report(
method: str, # fifo, lifo, specific_id
symbol: Optional[str] = Query(None),
year: Optional[int] = Query(None),
paper_trading: bool = Query(True),
):
"""Generate tax report using specified method."""
try:
if year is None:
year = datetime.now().year
tax_reporter = get_tax_reporter()
if method == "fifo":
if symbol:
events = tax_reporter.generate_fifo_report(symbol, year, paper_trading)
else:
# Generate for all symbols
events = []
# Get all symbols from trades
db = get_database()
async with db.get_session() as session:
stmt = select(Trade.symbol).distinct()
result = await session.execute(stmt)
symbols = result.scalars().all()
for sym in symbols:
events.extend(tax_reporter.generate_fifo_report(sym, year, paper_trading))
elif method == "lifo":
if symbol:
events = tax_reporter.generate_lifo_report(symbol, year, paper_trading)
else:
events = []
db = get_database()
async with db.get_session() as session:
stmt = select(Trade.symbol).distinct()
result = await session.execute(stmt)
symbols = result.scalars().all()
for sym in symbols:
events.extend(tax_reporter.generate_lifo_report(sym, year, paper_trading))
else:
raise HTTPException(status_code=400, detail=f"Unsupported tax method: {method}")
# Generate CSV
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(['Date', 'Symbol', 'Quantity', 'Cost Basis', 'Proceeds', 'Gain/Loss', 'Buy Date'])
for event in events:
writer.writerow([
event.get('date', ''),
event.get('symbol', ''),
event.get('quantity', 0),
event.get('cost_basis', 0),
event.get('proceeds', 0),
event.get('gain_loss', 0),
event.get('buy_date', ''),
])
output.seek(0)
return StreamingResponse(
iter([output.getvalue()]),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=tax_report_{method}_{year}_{datetime.now().strftime('%Y%m%d')}.csv"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
def get_tax_reporter():
"""Get tax reporter instance."""
from src.reporting.tax_reporter import get_tax_reporter as _get_tax_reporter
return _get_tax_reporter()

155
backend/api/reports.py Normal file
View File

@@ -0,0 +1,155 @@
"""Reports API endpoints for background report generation."""
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
router = APIRouter()
class ReportRequest(BaseModel):
"""Request model for report generation."""
report_type: str # 'performance', 'trades', 'tax', 'backtest'
format: str = "pdf" # 'pdf' or 'csv'
year: Optional[int] = None # For tax reports
method: Optional[str] = "fifo" # For tax reports: 'fifo', 'lifo'
class ExportRequest(BaseModel):
"""Request model for data export."""
export_type: str # 'orders', 'positions'
@router.post("/generate")
async def generate_report(request: ReportRequest):
"""Generate a report in the background.
This endpoint queues a report generation task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Supported report types:
- performance: Portfolio performance report
- trades: Trade history export
- tax: Tax report with capital gains calculation
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import generate_report_task
params = {
"format": request.format,
}
if request.report_type == "tax":
from datetime import datetime
params["year"] = request.year or datetime.now().year
params["method"] = request.method
task = generate_report_task.delay(
report_type=request.report_type,
params=params
)
return {
"task_id": task.id,
"status": "queued",
"report_type": request.report_type,
"format": request.format
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/export")
async def export_data(request: ExportRequest):
"""Export data in the background.
This endpoint queues a data export task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Supported export types:
- orders: Order history
- positions: Current/historical positions
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import export_data_task
task = export_data_task.delay(
export_type=request.export_type,
params={}
)
return {
"task_id": task.id,
"status": "queued",
"export_type": request.export_type
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/list")
async def list_reports():
"""List available reports in the reports directory."""
try:
from pathlib import Path
import os
reports_dir = Path(os.path.expanduser("~/.local/share/crypto_trader/reports"))
if not reports_dir.exists():
return {"reports": []}
reports = []
for f in reports_dir.iterdir():
if f.is_file():
stat = f.stat()
reports.append({
"name": f.name,
"path": str(f),
"size": stat.st_size,
"created": stat.st_mtime,
"type": f.suffix.lstrip(".")
})
# Sort by creation time, newest first
reports.sort(key=lambda x: x["created"], reverse=True)
return {"reports": reports}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{filename}")
async def delete_report(filename: str):
"""Delete a generated report."""
try:
from pathlib import Path
import os
reports_dir = Path(os.path.expanduser("~/.local/share/crypto_trader/reports"))
filepath = reports_dir / filename
if not filepath.exists():
raise HTTPException(status_code=404, detail="Report not found")
# Security check: ensure the file is actually in reports dir
if not str(filepath.resolve()).startswith(str(reports_dir.resolve())):
raise HTTPException(status_code=403, detail="Access denied")
filepath.unlink()
return {"status": "deleted", "filename": filename}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

359
backend/api/settings.py Normal file
View File

@@ -0,0 +1,359 @@
from typing import Dict, Any, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from src.core.database import Exchange, get_database
from src.core.config import get_config
from src.security.key_manager import get_key_manager
from src.trading.paper_trading import get_paper_trading
router = APIRouter()
class RiskSettings(BaseModel):
max_drawdown_percent: float
daily_loss_limit_percent: float
position_size_percent: float
class PaperTradingSettings(BaseModel):
initial_capital: float
fee_exchange: str = "coinbase" # Which exchange's fee model to use
class LoggingSettings(BaseModel):
level: str
dir: str
retention_days: int
class GeneralSettings(BaseModel):
timezone: str = "UTC"
theme: str = "dark"
currency: str = "USD"
class ExchangeCreate(BaseModel):
name: str
api_key: Optional[str] = None
api_secret: Optional[str] = None
sandbox: bool = False
read_only: bool = True
enabled: bool = True
class ExchangeUpdate(BaseModel):
api_key: Optional[str] = None
api_secret: Optional[str] = None
sandbox: Optional[bool] = None
read_only: Optional[bool] = None
enabled: Optional[bool] = None
@router.get("/risk")
async def get_risk_settings():
"""Get risk management settings."""
try:
config = get_config()
return {
"max_drawdown_percent": config.get("risk.max_drawdown_percent", 20.0),
"daily_loss_limit_percent": config.get("risk.daily_loss_limit_percent", 5.0),
"position_size_percent": config.get("risk.position_size_percent", 2.0),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/risk")
async def update_risk_settings(settings: RiskSettings):
"""Update risk management settings."""
try:
config = get_config()
config.set("risk.max_drawdown_percent", settings.max_drawdown_percent)
config.set("risk.daily_loss_limit_percent", settings.daily_loss_limit_percent)
config.set("risk.position_size_percent", settings.position_size_percent)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/paper-trading")
async def get_paper_trading_settings():
"""Get paper trading settings."""
try:
config = get_config()
fee_exchange = config.get("paper_trading.fee_exchange", "coinbase")
# Get fee rates for current exchange
fee_rates = config.get(f"trading.exchanges.{fee_exchange}.fees",
config.get("trading.default_fees", {"maker": 0.001, "taker": 0.001}))
return {
"initial_capital": config.get("paper_trading.default_capital", 100.0),
"fee_exchange": fee_exchange,
"fee_rates": fee_rates,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/paper-trading")
async def update_paper_trading_settings(settings: PaperTradingSettings):
"""Update paper trading settings."""
try:
config = get_config()
config.set("paper_trading.default_capital", settings.initial_capital)
config.set("paper_trading.fee_exchange", settings.fee_exchange)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/paper-trading/fee-exchanges")
async def get_available_fee_exchanges():
"""Get available exchange fee models for paper trading."""
try:
config = get_config()
exchanges_config = config.get("trading.exchanges", {})
default_fees = config.get("trading.default_fees", {"maker": 0.001, "taker": 0.001})
current = config.get("paper_trading.fee_exchange", "coinbase")
exchanges = [{"name": "default", "fees": default_fees}]
for name, data in exchanges_config.items():
if "fees" in data:
exchanges.append({
"name": name,
"fees": data["fees"]
})
return {
"exchanges": exchanges,
"current": current,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/paper-trading/reset")
async def reset_paper_account():
"""Reset paper trading account."""
try:
paper_trading = get_paper_trading()
# Reset to capital from config
success = await paper_trading.reset()
if success:
return {"status": "success", "message": "Paper account reset successfully"}
else:
raise HTTPException(status_code=500, detail="Failed to reset paper account")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/logging")
async def get_logging_settings():
"""Get logging settings."""
try:
config = get_config()
return {
"level": config.get("logging.level", "INFO"),
"dir": config.get("logging.dir", ""),
"retention_days": config.get("logging.retention_days", 30),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/logging")
async def update_logging_settings(settings: LoggingSettings):
"""Update logging settings."""
try:
config = get_config()
config.set("logging.level", settings.level)
config.set("logging.dir", settings.dir)
config.set("logging.retention_days", settings.retention_days)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/general")
async def get_general_settings():
"""Get general settings."""
try:
config = get_config()
return {
"timezone": config.get("general.timezone", "UTC"),
"theme": config.get("general.theme", "dark"),
"currency": config.get("general.currency", "USD"),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/general")
async def update_general_settings(settings: GeneralSettings):
"""Update general settings."""
try:
config = get_config()
config.set("general.timezone", settings.timezone)
config.set("general.theme", settings.theme)
config.set("general.currency", settings.currency)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/exchanges")
async def create_exchange(exchange: ExchangeCreate):
"""Create a new exchange."""
try:
db = get_database()
async with db.get_session() as session:
from src.exchanges.factory import ExchangeFactory
from src.exchanges.public_data import PublicDataAdapter
# Check if this is a public data exchange
adapter_class = None
try:
if hasattr(ExchangeFactory, '_adapters'):
adapter_class = ExchangeFactory._adapters.get(exchange.name.lower())
except:
pass
is_public_data = adapter_class == PublicDataAdapter if adapter_class else False
# Only require API keys for non-public-data exchanges
if not is_public_data:
if not exchange.api_key or not exchange.api_secret:
raise HTTPException(status_code=400, detail="API key and secret are required")
new_exchange = Exchange(
name=exchange.name,
enabled=exchange.enabled
)
session.add(new_exchange)
await session.flush()
# Save credentials
key_manager = get_key_manager()
key_manager.update_exchange(
new_exchange.id,
api_key=exchange.api_key or "",
api_secret=exchange.api_secret or "",
read_only=exchange.read_only if not is_public_data else True,
sandbox=exchange.sandbox if not is_public_data else False
)
await session.commit()
return {"id": new_exchange.id, "name": new_exchange.name, "status": "created"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/exchanges/{exchange_id}")
async def update_exchange(exchange_id: int, exchange: ExchangeUpdate):
"""Update an existing exchange."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange_obj = result.scalar_one_or_none()
if not exchange_obj:
raise HTTPException(status_code=404, detail="Exchange not found")
key_manager = get_key_manager()
credentials = key_manager.get_exchange_credentials(exchange_id)
# Update credentials if provided
if exchange.api_key is not None or exchange.api_secret is not None:
key_manager.update_exchange(
exchange_id,
api_key=exchange.api_key or credentials.get('api_key', ''),
api_secret=exchange.api_secret or credentials.get('api_secret', ''),
read_only=exchange.read_only if exchange.read_only is not None else credentials.get('read_only', True),
sandbox=exchange.sandbox if exchange.sandbox is not None else credentials.get('sandbox', False)
)
if exchange.enabled is not None:
exchange_obj.enabled = exchange.enabled
await session.commit()
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/exchanges/{exchange_id}")
async def delete_exchange(exchange_id: int):
"""Delete an exchange."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange = result.scalar_one_or_none()
if not exchange:
raise HTTPException(status_code=404, detail="Exchange not found")
await session.delete(exchange)
await session.commit()
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/exchanges/{exchange_id}/test")
async def test_exchange_connection(exchange_id: int):
"""Test exchange connection."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange = result.scalar_one_or_none()
if not exchange:
raise HTTPException(status_code=404, detail="Exchange not found")
from src.exchanges.factory import ExchangeFactory
from src.exchanges.public_data import PublicDataAdapter
# Get adapter class safely
adapter_class = None
try:
if hasattr(ExchangeFactory, '_adapters'):
adapter_class = ExchangeFactory._adapters.get(exchange.name.lower())
except:
pass
is_public_data = adapter_class == PublicDataAdapter if adapter_class else False
key_manager = get_key_manager()
if not is_public_data and not key_manager.get_exchange_credentials(exchange_id):
raise HTTPException(status_code=400, detail="No credentials found for this exchange")
adapter = ExchangeFactory.create(exchange_id)
if adapter and adapter.connect():
try:
if is_public_data:
adapter.get_ticker("BTC/USDT")
else:
adapter.get_balance()
return {"status": "success", "message": "Connection successful"}
except Exception as e:
return {"status": "error", "message": f"Connected but failed to fetch data: {str(e)}"}
else:
return {"status": "error", "message": "Failed to connect"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

310
backend/api/strategies.py Normal file
View File

@@ -0,0 +1,310 @@
"""Strategy API endpoints."""
from typing import List
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy import select
from ..core.dependencies import get_strategy_registry
from ..core.schemas import (
StrategyCreate, StrategyUpdate, StrategyResponse
)
from src.core.database import Strategy, get_database
from src.strategies.scheduler import get_scheduler as _get_scheduler
def get_strategy_scheduler():
"""Get strategy scheduler instance."""
return _get_scheduler()
router = APIRouter()
@router.get("/", response_model=List[StrategyResponse])
async def list_strategies():
"""List all strategies."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).order_by(Strategy.created_at.desc())
result = await session.execute(stmt)
strategies = result.scalars().all()
return [StrategyResponse.model_validate(s) for s in strategies]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/available")
async def list_available_strategies(
registry=Depends(get_strategy_registry)
):
"""List available strategy types."""
try:
return {"strategies": registry.list_available()}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/", response_model=StrategyResponse)
async def create_strategy(strategy_data: StrategyCreate):
"""Create a new strategy."""
try:
db = get_database()
async with db.get_session() as session:
strategy = Strategy(
name=strategy_data.name,
description=strategy_data.description,
strategy_type=strategy_data.strategy_type,
class_name=strategy_data.class_name,
parameters=strategy_data.parameters,
timeframes=strategy_data.timeframes,
paper_trading=strategy_data.paper_trading,
schedule=strategy_data.schedule,
enabled=False
)
session.add(strategy)
await session.commit()
await session.refresh(strategy)
return StrategyResponse.model_validate(strategy)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{strategy_id}", response_model=StrategyResponse)
async def get_strategy(strategy_id: int):
"""Get strategy by ID."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
return StrategyResponse.model_validate(strategy)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{strategy_id}", response_model=StrategyResponse)
async def update_strategy(strategy_id: int, strategy_data: StrategyUpdate):
"""Update a strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
if strategy_data.name is not None:
strategy.name = strategy_data.name
if strategy_data.description is not None:
strategy.description = strategy_data.description
if strategy_data.parameters is not None:
strategy.parameters = strategy_data.parameters
if strategy_data.timeframes is not None:
strategy.timeframes = strategy_data.timeframes
if strategy_data.enabled is not None:
strategy.enabled = strategy_data.enabled
if strategy_data.schedule is not None:
strategy.schedule = strategy_data.schedule
await session.commit()
await session.refresh(strategy)
return StrategyResponse.model_validate(strategy)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{strategy_id}")
async def delete_strategy(strategy_id: int):
"""Delete a strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
await session.delete(strategy)
await session.commit()
return {"status": "deleted", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_id}/start")
async def start_strategy(strategy_id: int):
"""Start a strategy manually (bypasses Autopilot)."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
# Start strategy via scheduler
scheduler = get_strategy_scheduler()
scheduler.start_strategy(strategy_id)
strategy.running = True # Only set running, not enabled
await session.commit()
return {"status": "started", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_id}/stop")
async def stop_strategy(strategy_id: int):
"""Stop a manually running strategy."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
# Stop strategy via scheduler
scheduler = get_strategy_scheduler()
scheduler.stop_strategy(strategy_id)
strategy.running = False # Only set running, not enabled
await session.commit()
return {"status": "stopped", "strategy_id": strategy_id}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{strategy_type}/optimize")
async def optimize_strategy(
strategy_type: str,
symbol: str = "BTC/USD",
method: str = "genetic",
population_size: int = 50,
generations: int = 100
):
"""Optimize strategy parameters using genetic algorithm.
This endpoint queues an optimization task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Args:
strategy_type: Type of strategy to optimize (e.g., 'rsi', 'macd')
symbol: Trading symbol for backtesting
method: Optimization method ('genetic', 'grid')
population_size: Population size for genetic algorithm
generations: Number of generations
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import optimize_strategy_task
# Get parameter ranges for the strategy type
registry = get_strategy_registry()
strategy_class = registry.get(strategy_type)
if not strategy_class:
raise HTTPException(status_code=404, detail=f"Strategy type '{strategy_type}' not found")
# Default parameter ranges based on strategy type
param_ranges = {
"rsi": {"period": (5, 50), "overbought": (60, 90), "oversold": (10, 40)},
"macd": {"fast_period": (5, 20), "slow_period": (15, 40), "signal_period": (5, 15)},
"moving_average": {"short_period": (5, 30), "long_period": (20, 100)},
"bollinger_mean_reversion": {"period": (10, 50), "std_dev": (1.5, 3.0)},
}.get(strategy_type.lower(), {"period": (10, 50)})
# Queue the optimization task
task = optimize_strategy_task.delay(
strategy_type=strategy_type,
symbol=symbol,
param_ranges=param_ranges,
method=method,
population_size=population_size,
generations=generations
)
return {
"task_id": task.id,
"status": "queued",
"strategy_type": strategy_type,
"symbol": symbol,
"method": method
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{strategy_id}/status")
async def get_strategy_status(strategy_id: int):
"""Get real-time status of a running strategy.
Returns execution info including last tick time, last signal, and stats.
"""
try:
scheduler = get_strategy_scheduler()
status = scheduler.get_strategy_status(strategy_id)
if not status:
# Check if strategy exists but isn't running
db = get_database()
async with db.get_session() as session:
stmt = select(Strategy).where(Strategy.id == strategy_id)
result = await session.execute(stmt)
strategy = result.scalar_one_or_none()
if not strategy:
raise HTTPException(status_code=404, detail="Strategy not found")
return {
"strategy_id": strategy_id,
"name": strategy.name,
"type": strategy.strategy_type,
"symbol": strategy.parameters.get('symbol') if strategy.parameters else None,
"running": False,
"enabled": strategy.enabled,
}
return status
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/running/all")
async def get_all_running_strategies():
"""Get status of all currently running strategies."""
try:
scheduler = get_strategy_scheduler()
active = scheduler.get_all_active_strategies()
return {
"total_running": len(active),
"strategies": active
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

206
backend/api/trading.py Normal file
View File

@@ -0,0 +1,206 @@
"""Trading API endpoints."""
from decimal import Decimal
from typing import List, Optional
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from ..core.dependencies import get_trading_engine, get_db_session
from ..core.schemas import OrderCreate, OrderResponse, PositionResponse
from src.core.database import Order, OrderSide, OrderType, OrderStatus
from src.core.repositories import OrderRepository, PositionRepository
from src.core.logger import get_logger
from src.trading.paper_trading import get_paper_trading
router = APIRouter()
logger = get_logger(__name__)
@router.post("/orders", response_model=OrderResponse)
async def create_order(
order_data: OrderCreate,
trading_engine=Depends(get_trading_engine)
):
"""Create and execute a trading order."""
try:
# Convert string enums to actual enums
side = OrderSide(order_data.side.value)
order_type = OrderType(order_data.order_type.value)
order = await trading_engine.execute_order(
exchange_id=order_data.exchange_id,
strategy_id=order_data.strategy_id,
symbol=order_data.symbol,
side=side,
order_type=order_type,
quantity=order_data.quantity,
price=order_data.price,
paper_trading=order_data.paper_trading
)
if not order:
raise HTTPException(status_code=400, detail="Order execution failed")
return OrderResponse.model_validate(order)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/orders", response_model=List[OrderResponse])
async def get_orders(
paper_trading: bool = True,
limit: int = 100,
db: AsyncSession = Depends(get_db_session)
):
"""Get order history."""
try:
repo = OrderRepository(db)
orders = await repo.get_all(limit=limit)
# Filter by paper_trading in memory or add to repo method (repo returns all by default currently sorted by date)
# Let's verify repo method. It has limit/offset but not filtering.
# We should filter here or improve repo.
# For this refactor, let's filter in python for simplicity or assume get_all needs an update.
# Ideally, update repo. But strictly following "get_all" contract.
filtered = [o for o in orders if o.paper_trading == paper_trading]
return [OrderResponse.model_validate(order) for order in filtered]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/orders/{order_id}", response_model=OrderResponse)
async def get_order(
order_id: int,
db: AsyncSession = Depends(get_db_session)
):
"""Get order by ID."""
try:
repo = OrderRepository(db)
order = await repo.get_by_id(order_id)
if not order:
raise HTTPException(status_code=404, detail="Order not found")
return OrderResponse.model_validate(order)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/orders/{order_id}/cancel")
async def cancel_order(
order_id: int,
trading_engine=Depends(get_trading_engine)
):
try:
# We can use trading_engine's cancel which handles DB and Exchange
success = await trading_engine.cancel_order(order_id)
if not success:
raise HTTPException(status_code=400, detail="Failed to cancel order or order not found")
return {"status": "cancelled", "order_id": order_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/orders/cancel-all")
async def cancel_all_orders(
paper_trading: bool = True,
trading_engine=Depends(get_trading_engine),
db: AsyncSession = Depends(get_db_session)
):
"""Cancel all open orders."""
try:
repo = OrderRepository(db)
open_orders = await repo.get_open_orders(paper_trading=paper_trading)
if not open_orders:
return {"status": "no_orders", "cancelled_count": 0}
cancelled_count = 0
failed_count = 0
for order in open_orders:
try:
if await trading_engine.cancel_order(order.id):
cancelled_count += 1
else:
failed_count += 1
except Exception as e:
logger.error(f"Failed to cancel order {order.id}: {e}")
failed_count += 1
return {
"status": "completed",
"cancelled_count": cancelled_count,
"failed_count": failed_count,
"total_orders": len(open_orders)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/positions", response_model=List[PositionResponse])
async def get_positions(
paper_trading: bool = True,
db: AsyncSession = Depends(get_db_session)
):
"""Get current positions."""
try:
if paper_trading:
paper_trading_sim = get_paper_trading()
positions = paper_trading_sim.get_positions()
# positions is a List[Position], convert to PositionResponse list
position_list = []
for pos in positions:
# pos is a Position database object
current_price = pos.current_price if pos.current_price else pos.entry_price
unrealized_pnl = pos.unrealized_pnl if pos.unrealized_pnl else Decimal(0)
realized_pnl = pos.realized_pnl if pos.realized_pnl else Decimal(0)
position_list.append(
PositionResponse(
symbol=pos.symbol,
quantity=pos.quantity,
entry_price=pos.entry_price,
current_price=current_price,
unrealized_pnl=unrealized_pnl,
realized_pnl=realized_pnl
)
)
return position_list
else:
# Live trading positions from database
repo = PositionRepository(db)
positions = await repo.get_all(paper_trading=False)
return [
PositionResponse(
symbol=pos.symbol,
quantity=pos.quantity,
entry_price=pos.entry_price,
current_price=pos.current_price or pos.entry_price,
unrealized_pnl=pos.unrealized_pnl,
realized_pnl=pos.realized_pnl
)
for pos in positions
]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/balance")
async def get_balance(paper_trading: bool = True):
"""Get account balance."""
try:
paper_trading_sim = get_paper_trading()
if paper_trading:
balance = paper_trading_sim.get_balance()
performance = paper_trading_sim.get_performance()
return {
"balance": float(balance),
"performance": performance
}
else:
# Live trading balance from exchange
return {"balance": 0.0, "performance": {}}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

242
backend/api/websocket.py Normal file
View File

@@ -0,0 +1,242 @@
"""WebSocket endpoints for real-time updates."""
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import List, Dict, Set, Callable, Optional
import json
import asyncio
from datetime import datetime
from decimal import Decimal
from collections import deque
from ..core.schemas import PriceUpdate, OrderUpdate
from src.data.pricing_service import get_pricing_service
router = APIRouter()
class ConnectionManager:
"""Manages WebSocket connections."""
def __init__(self):
self.active_connections: List[WebSocket] = []
self.subscribed_symbols: Set[str] = set()
self._pricing_service = None
self._price_callbacks: Dict[str, List[Callable]] = {}
# Queue for price updates (thread-safe for async processing)
self._price_update_queue: deque = deque()
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._processing_task = None
def set_event_loop(self, loop: asyncio.AbstractEventLoop):
"""Set the event loop for async operations."""
self._loop = loop
async def start_background_tasks(self):
"""Start background processing tasks."""
if self._processing_task is None or self._processing_task.done():
self._processing_task = asyncio.create_task(self._process_queue())
print("WebSocket manager background tasks started")
async def _process_queue(self):
"""Periodically process price updates from queue."""
while True:
try:
if self._price_update_queue:
# Process up to 10 updates at a time to prevent blocking
for _ in range(10):
if not self._price_update_queue:
break
update = self._price_update_queue.popleft()
await self.broadcast_price_update(
exchange=update["exchange"],
symbol=update["symbol"],
price=update["price"]
)
await asyncio.sleep(0.01) # Check queue frequently but yield
except Exception as e:
print(f"Error processing price update queue: {e}")
await asyncio.sleep(1)
def _initialize_pricing_service(self):
"""Initialize pricing service and subscribe to price updates."""
if self._pricing_service is None:
self._pricing_service = get_pricing_service()
def subscribe_to_symbol(self, symbol: str):
"""Subscribe to price updates for a symbol."""
self._initialize_pricing_service()
if symbol not in self.subscribed_symbols:
self.subscribed_symbols.add(symbol)
def price_callback(data):
"""Callback for price updates from pricing service."""
# Store update in queue for async processing
update = {
"exchange": "pricing",
"symbol": data.get('symbol', symbol),
"price": Decimal(str(data.get('price', 0)))
}
self._price_update_queue.append(update)
# Note: We rely on the background task to process this queue now
self._pricing_service.subscribe_ticker(symbol, price_callback)
async def _process_price_update(self, update: Dict):
"""Process a price update asynchronously.
DEPRECATED: Use _process_queue instead.
"""
await self.broadcast_price_update(
exchange=update["exchange"],
symbol=update["symbol"],
price=update["price"]
)
def unsubscribe_from_symbol(self, symbol: str):
"""Unsubscribe from price updates for a symbol."""
if symbol in self.subscribed_symbols:
self.subscribed_symbols.remove(symbol)
if self._pricing_service:
self._pricing_service.unsubscribe_ticker(symbol)
async def connect(self, websocket: WebSocket):
"""Add WebSocket connection to active connections.
Note: websocket.accept() must be called before this method.
"""
self.active_connections.append(websocket)
# Ensure background task is running
await self.start_background_tasks()
def disconnect(self, websocket: WebSocket):
"""Remove a WebSocket connection."""
if websocket in self.active_connections:
self.active_connections.remove(websocket)
async def broadcast_price_update(self, exchange: str, symbol: str, price: Decimal):
"""Broadcast price update to all connected clients."""
message = PriceUpdate(
exchange=exchange,
symbol=symbol,
price=price,
timestamp=datetime.utcnow()
)
await self.broadcast(message.dict())
async def broadcast_order_update(self, order_id: int, status: str, filled_quantity: Decimal = None):
"""Broadcast order update to all connected clients."""
message = OrderUpdate(
order_id=order_id,
status=status,
filled_quantity=filled_quantity,
timestamp=datetime.utcnow()
)
await self.broadcast(message.dict())
async def broadcast_training_progress(self, step: str, progress: int, total: int, message: str, details: dict = None):
"""Broadcast training progress update to all connected clients."""
update = {
"type": "training_progress",
"step": step,
"progress": progress,
"total": total,
"percent": int((progress / total) * 100) if total > 0 else 0,
"message": message,
"details": details or {},
"timestamp": datetime.utcnow().isoformat()
}
await self.broadcast(update)
async def broadcast(self, message: dict):
"""Broadcast message to all connected clients."""
disconnected = []
for connection in self.active_connections:
try:
await connection.send_json(message)
except Exception:
disconnected.append(connection)
# Remove disconnected clients
for conn in disconnected:
self.disconnect(conn)
manager = ConnectionManager()
@router.websocket("/")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint for real-time updates."""
# Check origin for CORS before accepting
origin = websocket.headers.get("origin")
allowed_origins = ["http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000", "http://127.0.0.1:5173"]
# Allow connections from allowed origins or if no origin header (direct connections, testing)
# Relaxed check: Log warning but allow if origin doesn't match, to prevent disconnection issues in some environments
if origin and origin not in allowed_origins:
print(f"Warning: WebSocket connection from unknown origin: {origin}")
# We allow it for now to fix disconnection issues, but normally we might block
# await websocket.close(code=1008, reason="Origin not allowed")
# return
# Accept the connection
await websocket.accept()
try:
# Connect to manager (starts background tasks if needed)
await manager.connect(websocket)
subscribed_symbols = set()
while True:
# Receive messages from client (for subscriptions, etc.)
data = await websocket.receive_text()
try:
message = json.loads(data)
# Handle subscription messages
if message.get("type") == "subscribe":
symbol = message.get("symbol")
if symbol:
subscribed_symbols.add(symbol)
manager.subscribe_to_symbol(symbol)
await websocket.send_json({
"type": "subscription_confirmed",
"symbol": symbol
})
elif message.get("type") == "unsubscribe":
symbol = message.get("symbol")
if symbol and symbol in subscribed_symbols:
subscribed_symbols.remove(symbol)
manager.unsubscribe_from_symbol(symbol)
await websocket.send_json({
"type": "unsubscription_confirmed",
"symbol": symbol
})
else:
# Default acknowledgment
await websocket.send_json({"type": "ack", "message": "received"})
except json.JSONDecodeError:
await websocket.send_json({"type": "error", "message": "Invalid JSON"})
except Exception as e:
# Don't send internal errors to client in production, but okay for debugging
await websocket.send_json({"type": "error", "message": str(e)})
except WebSocketDisconnect:
# Clean up subscriptions
for symbol in subscribed_symbols:
manager.unsubscribe_from_symbol(symbol)
manager.disconnect(websocket)
except Exception as e:
manager.disconnect(websocket)
print(f"WebSocket error: {e}")
# Only close if not already closed
try:
await websocket.close(code=1011)
except Exception:
pass

1
backend/core/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Core backend utilities."""

View File

@@ -0,0 +1,70 @@
"""FastAPI dependencies for service injection."""
from functools import lru_cache
import sys
from pathlib import Path
# Add src to path - must be done before any imports
src_path = Path(__file__).parent.parent.parent / "src"
if str(src_path) not in sys.path:
sys.path.insert(0, str(src_path))
# Import database and redis immediately
from core.database import get_database as _get_database
from src.core.redis import get_redis_client as _get_redis_client
# Lazy imports for other services (only import when needed to avoid import errors)
# These will be imported on-demand in their respective getter functions
@lru_cache()
def get_database():
"""Get database instance."""
return _get_database()
async def get_db_session():
"""Get database session."""
db = get_database()
async with db.get_session() as session:
yield session
@lru_cache()
def get_trading_engine():
"""Get trading engine instance."""
from trading.engine import get_trading_engine as _get_trading_engine
return _get_trading_engine()
@lru_cache()
def get_portfolio_tracker():
"""Get portfolio tracker instance."""
from portfolio.tracker import get_portfolio_tracker as _get_portfolio_tracker
return _get_portfolio_tracker()
@lru_cache()
def get_strategy_registry():
"""Get strategy registry instance."""
from strategies.base import get_strategy_registry as _get_strategy_registry
return _get_strategy_registry()
@lru_cache()
def get_backtesting_engine():
"""Get backtesting engine instance."""
from backtesting.engine import get_backtest_engine as _get_backtesting_engine
return _get_backtesting_engine()
def get_exchange_factory():
"""Get exchange factory."""
from exchanges.factory import ExchangeFactory
return ExchangeFactory
@lru_cache()
def get_redis_client():
"""Get Redis client instance."""
return _get_redis_client()

213
backend/core/schemas.py Normal file
View File

@@ -0,0 +1,213 @@
"""Pydantic schemas for request/response validation."""
from datetime import datetime, timezone
from decimal import Decimal
from typing import Optional, List, Dict, Any
from pydantic import BaseModel, Field, ConfigDict, field_validator
from enum import Enum
class OrderSide(str, Enum):
"""Order side."""
BUY = "buy"
SELL = "sell"
class OrderType(str, Enum):
"""Order type."""
MARKET = "market"
LIMIT = "limit"
STOP_LOSS = "stop_loss"
TAKE_PROFIT = "take_profit"
TRAILING_STOP = "trailing_stop"
OCO = "oco"
ICEBERG = "iceberg"
class OrderStatus(str, Enum):
"""Order status."""
PENDING = "pending"
OPEN = "open"
PARTIALLY_FILLED = "partially_filled"
FILLED = "filled"
CANCELLED = "cancelled"
REJECTED = "rejected"
EXPIRED = "expired"
# Trading Schemas
class OrderCreate(BaseModel):
"""Create order request."""
exchange_id: int
symbol: str
side: OrderSide
order_type: OrderType
quantity: Decimal
price: Optional[Decimal] = None
strategy_id: Optional[int] = None
paper_trading: bool = True
class OrderResponse(BaseModel):
"""Order response."""
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
id: int
exchange_id: int
strategy_id: Optional[int]
symbol: str
order_type: OrderType
side: OrderSide
status: OrderStatus
quantity: Decimal
price: Optional[Decimal]
filled_quantity: Decimal
average_fill_price: Optional[Decimal]
fee: Decimal
paper_trading: bool
created_at: datetime
updated_at: datetime
filled_at: Optional[datetime]
@field_validator('created_at', 'updated_at', 'filled_at', mode='after')
@classmethod
def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]:
if v and v.tzinfo is None:
return v.replace(tzinfo=timezone.utc)
return v
class PositionResponse(BaseModel):
"""Position response."""
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
symbol: str
quantity: Decimal
entry_price: Decimal
current_price: Decimal
unrealized_pnl: Decimal
realized_pnl: Decimal
# Portfolio Schemas
class PortfolioResponse(BaseModel):
"""Portfolio response."""
positions: List[Dict[str, Any]]
performance: Dict[str, float]
timestamp: str
class PortfolioHistoryResponse(BaseModel):
"""Portfolio history response."""
dates: List[str]
values: List[float]
pnl: List[float]
# Strategy Schemas
class StrategyCreate(BaseModel):
"""Create strategy request."""
name: str
description: Optional[str] = None
strategy_type: str
class_name: str
parameters: Dict[str, Any] = Field(default_factory=dict)
timeframes: List[str] = Field(default_factory=lambda: ["1h"])
paper_trading: bool = True
schedule: Optional[Dict[str, Any]] = None
class StrategyUpdate(BaseModel):
"""Update strategy request."""
name: Optional[str] = None
description: Optional[str] = None
parameters: Optional[Dict[str, Any]] = None
timeframes: Optional[List[str]] = None
enabled: Optional[bool] = None
schedule: Optional[Dict[str, Any]] = None
class StrategyResponse(BaseModel):
"""Strategy response."""
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
id: int
name: str
description: Optional[str]
strategy_type: str
class_name: str
parameters: Dict[str, Any]
timeframes: List[str]
enabled: bool
running: bool = False
paper_trading: bool
version: str
schedule: Optional[Dict[str, Any]]
created_at: datetime
updated_at: datetime
@field_validator('created_at', 'updated_at', mode='after')
@classmethod
def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]:
if v and v.tzinfo is None:
return v.replace(tzinfo=timezone.utc)
return v
# Backtesting Schemas
class BacktestRequest(BaseModel):
"""Backtest request."""
strategy_id: int
symbol: str
exchange: str
timeframe: str
start_date: datetime
end_date: datetime
initial_capital: Decimal = Decimal("100.0")
slippage: float = 0.001
fee_rate: float = 0.001
class BacktestResponse(BaseModel):
"""Backtest response."""
backtest_id: Optional[int] = None
results: Dict[str, Any]
status: str = "completed"
# Exchange Schemas
class ExchangeResponse(BaseModel):
"""Exchange response."""
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
id: int
name: str
sandbox: bool
read_only: bool
enabled: bool
created_at: datetime
updated_at: datetime
@field_validator('created_at', 'updated_at', mode='after')
@classmethod
def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]:
if v and v.tzinfo is None:
return v.replace(tzinfo=timezone.utc)
return v
# WebSocket Messages
class PriceUpdate(BaseModel):
"""Price update message."""
exchange: str
symbol: str
price: Decimal
timestamp: datetime
class OrderUpdate(BaseModel):
"""Order update message."""
order_id: int
status: OrderStatus
filled_quantity: Optional[Decimal] = None
timestamp: datetime

185
backend/main.py Normal file
View File

@@ -0,0 +1,185 @@
"""FastAPI main application - Simplified Crypto Trader API."""
import sys
from pathlib import Path
# Set up import path correctly for relative imports to work
project_root = Path(__file__).parent.parent
src_path = project_root / "src"
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
if str(src_path) not in sys.path:
sys.path.insert(0, str(src_path))
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
import uvicorn
from .api import autopilot, market_data
from .core.dependencies import get_database
# Initialize Celery app configuration
import src.worker.app
app = FastAPI(
title="Crypto Trader API",
description="Simplified Cryptocurrency Trading Platform",
version="2.0.0"
)
# CORS middleware for frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000", "http://127.0.0.1:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Core routers (always required)
app.include_router(autopilot.router, prefix="/api/autopilot", tags=["autopilot"])
app.include_router(market_data.router, prefix="/api/market-data", tags=["market-data"])
# Trading and Portfolio
try:
from .api import trading
app.include_router(trading.router, prefix="/api/trading", tags=["trading"])
except ImportError as e:
print(f"Warning: Could not import trading router: {e}")
try:
from .api import portfolio
app.include_router(portfolio.router, prefix="/api/portfolio", tags=["portfolio"])
except ImportError as e:
print(f"Warning: Could not import portfolio router: {e}")
# Strategies and Backtesting
try:
from .api import strategies
app.include_router(strategies.router, prefix="/api/strategies", tags=["strategies"])
except ImportError as e:
print(f"Warning: Could not import strategies router: {e}")
try:
from .api import backtesting
app.include_router(backtesting.router, prefix="/api/backtesting", tags=["backtesting"])
except ImportError as e:
print(f"Warning: Could not import backtesting router: {e}")
# Settings (includes exchanges and alerts)
try:
from .api import exchanges
app.include_router(exchanges.router, prefix="/api/exchanges", tags=["exchanges"])
except ImportError as e:
print(f"Warning: Could not import exchanges router: {e}")
try:
from .api import settings
app.include_router(settings.router, prefix="/api/settings", tags=["settings"])
except ImportError as e:
print(f"Warning: Could not import settings router: {e}")
try:
from .api import alerts
app.include_router(alerts.router, prefix="/api/alerts", tags=["alerts"])
except ImportError as e:
print(f"Warning: Could not import alerts router: {e}")
# Reporting (merged into Portfolio UI but still needs API)
try:
from .api import reporting
app.include_router(reporting.router, prefix="/api/reporting", tags=["reporting"])
except ImportError as e:
print(f"Warning: Could not import reporting router: {e}")
# Reports (background generation)
try:
from .api import reports
app.include_router(reports.router, prefix="/api/reports", tags=["reports"])
except ImportError as e:
print(f"Warning: Could not import reports router: {e}")
# WebSocket endpoint
try:
from .api import websocket
app.include_router(websocket.router, prefix="/ws", tags=["websocket"])
except ImportError as e:
print(f"Warning: Could not import websocket router: {e}")
# Serve frontend static files (in production)
frontend_path = Path(__file__).parent.parent / "frontend" / "dist"
if frontend_path.exists():
static_path = frontend_path / "assets"
if static_path.exists():
app.mount("/assets", StaticFiles(directory=str(static_path)), name="assets")
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str):
"""Serve frontend SPA."""
if full_path.startswith(("api", "docs", "redoc", "openapi.json")):
from fastapi import HTTPException
raise HTTPException(status_code=404)
file_path = frontend_path / full_path
if file_path.exists() and file_path.is_file():
return FileResponse(str(file_path))
return FileResponse(str(frontend_path / "index.html"))
@app.on_event("startup")
async def startup_event():
"""Initialize services on startup."""
try:
from src.trading.paper_trading import get_paper_trading
db = get_database()
await db.create_tables()
# Verify connection
async with db.get_session() as session:
# Just checking connection
pass
# Initialize paper trading (seeds portfolio if needed)
await get_paper_trading().initialize()
print("✓ Database initialized")
print("✓ Crypto Trader API ready")
except Exception as e:
print(f"✗ Startup error: {e}")
# In production we might want to exit here, but for now just log
@app.on_event("shutdown")
async def shutdown_event():
"""Cleanup on shutdown."""
print("Shutting down Crypto Trader API...")
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
from fastapi.responses import JSONResponse
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)
@app.get("/api/health")
async def health_check():
"""Health check endpoint."""
return {"status": "ok", "service": "crypto_trader_api", "version": "2.0.0"}
if __name__ == "__main__":
uvicorn.run(
"backend.main:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info"
)

4
backend/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
python-multipart>=0.0.6
pydantic>=2.5.0

21
check_db.py Normal file
View File

@@ -0,0 +1,21 @@
import os
import asyncio
from src.core.database import get_database
from sqlalchemy import text
async def check_db():
try:
db = get_database()
session = db.get_session()
async with session:
result = await session.execute(text("SELECT id, symbol, side, order_type, quantity, status, created_at FROM orders ORDER BY id DESC LIMIT 20;"))
rows = result.fetchall()
for row in rows:
print(row)
except Exception as e:
print(f"Error: {e}")
finally:
await db.close()
if __name__ == "__main__":
asyncio.run(check_db())

94
config/config.yaml Normal file
View File

@@ -0,0 +1,94 @@
app:
name: Crypto Trader
version: 0.1.0
database:
type: postgresql
url: postgresql+asyncpg://trader:trader_password@localhost/crypto_trader
logging:
level: INFO
dir: ~/.local/share/crypto_trader/logs
retention_days: 30
rotation: daily
paper_trading:
enabled: true
default_capital: 10000.0
fee_exchange: coinbase
updates:
check_on_startup: true
repository_url: ''
exchanges: null
strategies:
default_timeframe: 1h
risk:
max_drawdown_percent: 20.0
daily_loss_limit_percent: 5.0
position_size_percent: 2.0
trading:
default_fees:
maker: 0.001
taker: 0.001
minimum: 0.0
exchanges:
coinbase:
fees:
maker: 0.004
taker: 0.006
minimum: 0.0
kraken:
fees:
maker: 0.0016
taker: 0.0026
minimum: 0.0
binance:
fees:
maker: 0.001
taker: 0.001
minimum: 0.0
data_providers:
primary:
- name: kraken
enabled: true
priority: 1
- name: coinbase
enabled: true
priority: 2
- name: binance
enabled: true
priority: 3
fallback:
name: coingecko
enabled: true
api_key: ''
caching:
ticker_ttl: 2
ohlcv_ttl: 60
max_cache_size: 1000
websocket:
enabled: true
reconnect_interval: 5
ping_interval: 30
redis:
host: 127.0.0.1
port: 6379
db: 0
password: null
socket_connect_timeout: 5
celery:
broker_url: redis://127.0.0.1:6379/0
result_backend: redis://127.0.0.1:6379/0
autopilot:
intelligent:
min_confidence_threshold: 0.75
max_trades_per_day: 10
min_profit_target: 0.02
enable_auto_execution: true
bootstrap:
days: 5
timeframe: 1m
min_samples_per_strategy: 30
symbols:
- ADA/USD
general:
timezone: America/New_York
theme: dark
currency: USD

54
config/logging.yaml Normal file
View File

@@ -0,0 +1,54 @@
# Logging Configuration
version: 1
disable_existing_loggers: false
formatters:
standard:
format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
detailed:
format: '%(asctime)s [%(levelname)s] %(name)s:%(lineno)d: %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
handlers:
console:
class: logging.StreamHandler
level: INFO
formatter: standard
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
level: DEBUG
formatter: detailed
filename: ~/.local/share/crypto_trader/logs/crypto_trader.log
maxBytes: 10485760 # 10MB
backupCount: 5
encoding: utf-8
loggers:
trading:
level: INFO
handlers: [console, file]
propagate: false
exchange:
level: INFO
handlers: [console, file]
propagate: false
strategy:
level: DEBUG
handlers: [console, file]
propagate: false
backtesting:
level: INFO
handlers: [console, file]
propagate: false
root:
level: INFO
handlers: [console, file]

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
version: '3.8'
services:
crypto-trader:
build:
context: .
dockerfile: Dockerfile
container_name: crypto-trader
ports:
- "8000:8000"
volumes:
- ./data:/app/data
- ./config:/app/config
environment:
- PYTHONPATH=/app
- PYTHONUNBUFFERED=1
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/api/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

20
docs/api/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/api/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

@@ -0,0 +1,29 @@
Alert System
============
Alert system for price, indicator, risk, and system notifications.
Alert Engine
------------
.. automodule:: src.alerts.engine
:members:
:undoc-members:
:show-inheritance:
Alert Manager
-------------
.. automodule:: src.alerts.manager
:members:
:undoc-members:
:show-inheritance:
Alert Channels
--------------
.. automodule:: src.alerts.channels
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,37 @@
Backtesting Engine
==================
The backtesting engine enables strategy evaluation on historical data.
Backtesting Engine
------------------
.. automodule:: src.backtesting.engine
:members:
:undoc-members:
:show-inheritance:
Performance Metrics
-------------------
.. automodule:: src.backtesting.metrics
:members:
:undoc-members:
:show-inheritance:
Slippage and Fees
-----------------
.. automodule:: src.backtesting.slippage
:members:
:undoc-members:
:show-inheritance:
Data Provider
-------------
.. automodule:: src.backtesting.data_provider
:members:
:undoc-members:
:show-inheritance:

80
docs/api/source/conf.py Normal file
View File

@@ -0,0 +1,80 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import sys
import os
# Add the project root to the path
sys.path.insert(0, os.path.abspath('../../..'))
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'Crypto Trader'
copyright = '2025, kfox'
author = 'kfox'
release = '0.1.0'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon', # For Google-style docstrings
'sphinx_autodoc_typehints', # Type hints in docs
]
templates_path = ['_templates']
exclude_patterns = []
# Napoleon settings for Google-style docstrings
napoleon_google_docstring = True
napoleon_numpy_docstring = False
napoleon_include_init_with_doc = False
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = False
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
# Autodoc settings
autodoc_default_options = {
'members': True,
'undoc-members': True,
'show-inheritance': True,
'special-members': '__init__',
}
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_theme_options = {
'collapse_navigation': False,
'display_version': True,
'logo_only': False,
}
# -- Options for intersphinx extension ---------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'pandas': ('https://pandas.pydata.org/docs/', None),
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
'ccxt': ('https://docs.ccxt.com/en/latest/', None),
}
# -- Options for todo extension ----------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration
todo_include_todos = True

94
docs/api/source/data.rst Normal file
View File

@@ -0,0 +1,94 @@
Data Collection and Indicators
================================
Data collection, storage, and technical indicator calculations.
Technical Indicators
--------------------
.. automodule:: src.data.indicators
:members:
:undoc-members:
:show-inheritance:
Data Collector
--------------
.. automodule:: src.data.collector
:members:
:undoc-members:
:show-inheritance:
Data Storage
------------
.. automodule:: src.data.storage
:members:
:undoc-members:
:show-inheritance:
Data Quality
------------
.. automodule:: src.data.quality
:members:
:undoc-members:
:show-inheritance:
Pricing Service
---------------
The unified pricing service manages multiple data providers with automatic failover.
.. automodule:: src.data.pricing_service
:members:
:undoc-members:
:show-inheritance:
Pricing Providers
-----------------
Base Provider Interface
~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: src.data.providers.base_provider
:members:
:undoc-members:
:show-inheritance:
CCXT Provider
~~~~~~~~~~~~~
.. automodule:: src.data.providers.ccxt_provider
:members:
:undoc-members:
:show-inheritance:
CoinGecko Provider
~~~~~~~~~~~~~~~~~~
.. automodule:: src.data.providers.coingecko_provider
:members:
:undoc-members:
:show-inheritance:
Cache Manager
-------------
Intelligent caching system for pricing data.
.. automodule:: src.data.cache_manager
:members:
:undoc-members:
:show-inheritance:
Health Monitor
--------------
Provider health monitoring and failover management.
.. automodule:: src.data.health_monitor
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,40 @@
Exchange Adapters
=================
The exchange adapter system provides a unified interface for trading on multiple cryptocurrency exchanges.
Base Exchange Adapter
---------------------
.. automodule:: src.exchanges.base
:members:
:undoc-members:
:show-inheritance:
Exchange Factory
----------------
.. automodule:: src.exchanges.factory
:members:
:undoc-members:
:show-inheritance:
Coinbase Exchange
-----------------
.. automodule:: src.exchanges.coinbase
:members:
:undoc-members:
:show-inheritance:
WebSocket Support
-----------------
The Coinbase adapter includes WebSocket subscription methods:
- ``subscribe_ticker()``: Subscribe to real-time price updates
- ``subscribe_orderbook()``: Subscribe to order book changes
- ``subscribe_trades()``: Subscribe to trade executions
These methods support real-time data streaming for UI updates.

27
docs/api/source/index.rst Normal file
View File

@@ -0,0 +1,27 @@
Crypto Trader API Documentation
================================
Welcome to the Crypto Trader API documentation. This documentation provides comprehensive reference for all public APIs, classes, and functions in the Crypto Trader application.
.. toctree::
:maxdepth: 2
:caption: API Reference:
modules
exchanges
strategies
trading
backtesting
portfolio
risk
data
market_data
alerts
security
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -0,0 +1,312 @@
Market Data API
===============
REST API endpoints for market data and pricing information.
Market Data Endpoints
---------------------
OHLCV Data
~~~~~~~~~~
**GET** ``/api/market-data/ohlcv/{symbol}``
Get OHLCV (Open, High, Low, Close, Volume) candlestick data for a symbol.
**Parameters:**
- ``symbol`` (path, required): Trading pair symbol (e.g., "BTC/USD")
- ``timeframe`` (query, optional): Timeframe for candles (default: "1h")
- Valid values: "1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"
- ``limit`` (query, optional): Number of candles to return (default: 100, max: 1000)
- ``exchange`` (query, optional): Exchange name (deprecated, uses pricing service)
**Response:**
.. code-block:: json
[
{
"time": 1609459200,
"open": 50000.0,
"high": 51000.0,
"low": 49000.0,
"close": 50000.0,
"volume": 1000.0
}
]
**Example:**
.. code-block:: bash
curl http://localhost:8000/api/market-data/ohlcv/BTC%2FUSD?timeframe=1h&limit=100
Ticker Data
~~~~~~~~~~~
**GET** ``/api/market-data/ticker/{symbol}``
Get current ticker data for a symbol, including price, volume, and provider information.
**Parameters:**
- ``symbol`` (path, required): Trading pair symbol (e.g., "BTC/USD")
**Response:**
.. code-block:: json
{
"symbol": "BTC/USD",
"bid": 50000.0,
"ask": 50001.0,
"last": 50000.5,
"high": 51000.0,
"low": 49000.0,
"volume": 1000000.0,
"timestamp": 1609459200000,
"provider": "CCXT-Kraken"
}
**Example:**
.. code-block:: bash
curl http://localhost:8000/api/market-data/ticker/BTC%2FUSD
Provider Health
~~~~~~~~~~~~~~~
**GET** ``/api/market-data/providers/health``
Get health status for pricing providers.
**Parameters:**
- ``provider`` (query, optional): Specific provider name to get health for
**Response:**
.. code-block:: json
{
"active_provider": "CCXT-Kraken",
"health": {
"CCXT-Kraken": {
"status": "healthy",
"last_check": "2025-01-01T12:00:00Z",
"last_success": "2025-01-01T12:00:00Z",
"success_count": 100,
"failure_count": 2,
"avg_response_time": 0.123,
"consecutive_failures": 0,
"circuit_breaker_open": false
}
}
}
**Example:**
.. code-block:: bash
curl http://localhost:8000/api/market-data/providers/health?provider=CCXT-Kraken
Provider Status
~~~~~~~~~~~~~~~
**GET** ``/api/market-data/providers/status``
Get detailed status for all pricing providers, including cache statistics.
**Response:**
.. code-block:: json
{
"active_provider": "CCXT-Kraken",
"providers": {
"CCXT-Kraken": {
"status": "healthy",
"last_check": "2025-01-01T12:00:00Z",
"success_count": 100,
"failure_count": 2,
"avg_response_time": 0.123
},
"CoinGecko": {
"status": "unknown",
"success_count": 0,
"failure_count": 0
}
},
"cache": {
"size": 50,
"max_size": 1000,
"hits": 1000,
"misses": 200,
"hit_rate": 83.33,
"evictions": 0,
"avg_age_seconds": 30.5
}
}
**Example:**
.. code-block:: bash
curl http://localhost:8000/api/market-data/providers/status
Provider Configuration
~~~~~~~~~~~~~~~~~~~~~~
**GET** ``/api/market-data/providers/config``
Get current provider configuration.
**Response:**
.. code-block:: json
{
"primary": [
{
"name": "kraken",
"enabled": true,
"priority": 1
},
{
"name": "coinbase",
"enabled": true,
"priority": 2
}
],
"fallback": {
"name": "coingecko",
"enabled": true,
"api_key": ""
},
"caching": {
"ticker_ttl": 2,
"ohlcv_ttl": 60,
"max_cache_size": 1000
},
"websocket": {
"enabled": true,
"reconnect_interval": 5,
"ping_interval": 30
}
}
**Example:**
.. code-block:: bash
curl http://localhost:8000/api/market-data/providers/config
Update Provider Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**PUT** ``/api/market-data/providers/config``
Update provider configuration.
**Request Body:**
.. code-block:: json
{
"primary": [
{
"name": "kraken",
"enabled": true,
"priority": 1
}
],
"caching": {
"ticker_ttl": 5,
"ohlcv_ttl": 120
}
}
**Response:**
.. code-block:: json
{
"message": "Configuration updated successfully",
"config": {
"primary": [...],
"fallback": {...},
"caching": {...},
"websocket": {...}
}
}
**Example:**
.. code-block:: bash
curl -X PUT http://localhost:8000/api/market-data/providers/config \
-H "Content-Type: application/json" \
-d '{"caching": {"ticker_ttl": 5}}'
WebSocket Updates
-----------------
The WebSocket endpoint at ``/ws`` broadcasts real-time price updates.
**Connection:**
.. code-block:: javascript
const ws = new WebSocket('ws://localhost:8000/ws');
ws.onopen = () => {
// Subscribe to symbol updates
ws.send(JSON.stringify({
type: 'subscribe',
symbol: 'BTC/USD'
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'price_update') {
console.log('Price update:', message);
}
};
**Message Types:**
- ``subscribe``: Subscribe to price updates for a symbol
- ``unsubscribe``: Unsubscribe from price updates
- ``price_update``: Price update broadcast (contains exchange, symbol, price, timestamp)
- ``provider_status_update``: Provider status change notification
**Price Update Message:**
.. code-block:: json
{
"type": "price_update",
"exchange": "pricing",
"symbol": "BTC/USD",
"price": "50000.50",
"timestamp": "2025-01-01T12:00:00Z"
}
Error Responses
---------------
All endpoints may return standard HTTP error codes:
- ``400 Bad Request``: Invalid request parameters
- ``404 Not Found``: Symbol or provider not found
- ``500 Internal Server Error``: Server error
Error response format:
.. code-block:: json
{
"detail": "Error message describing what went wrong"
}

View File

@@ -0,0 +1,18 @@
Modules
=====
.. automodule:: src.core
:members:
:undoc-members:
:show-inheritance:
Core Modules
------------
.. toctree::
:maxdepth: 2
core/config
core/database
core/logger

View File

@@ -0,0 +1,21 @@
Portfolio Management
====================
Portfolio tracking and analytics for monitoring trading performance.
Portfolio Tracker
-----------------
.. automodule:: src.portfolio.tracker
:members:
:undoc-members:
:show-inheritance:
Portfolio Analytics
-------------------
.. automodule:: src.portfolio.analytics
:members:
:undoc-members:
:show-inheritance:

37
docs/api/source/risk.rst Normal file
View File

@@ -0,0 +1,37 @@
Risk Management
===============
Risk management system for controlling trading exposure and losses.
Risk Manager
------------
.. automodule:: src.risk.manager
:members:
:undoc-members:
:show-inheritance:
Stop Loss
---------
.. automodule:: src.risk.stop_loss
:members:
:undoc-members:
:show-inheritance:
Position Sizing
---------------
.. automodule:: src.risk.position_sizing
:members:
:undoc-members:
:show-inheritance:
Risk Limits
-----------
.. automodule:: src.risk.limits
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,29 @@
Security
========
Security features for API key management and encryption.
Encryption
----------
.. automodule:: src.security.encryption
:members:
:undoc-members:
:show-inheritance:
Key Manager
-----------
.. automodule:: src.security.key_manager
:members:
:undoc-members:
:show-inheritance:
Audit Logging
-------------
.. automodule:: src.security.audit
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,120 @@
Strategy Framework
==================
The strategy framework enables creation and management of trading strategies.
Base Strategy
-------------
.. automodule:: src.strategies.base
:members:
:undoc-members:
:show-inheritance:
Strategy Registry
-----------------
.. automodule:: src.strategies.base
:members: StrategyRegistry
:undoc-members:
:show-inheritance:
Technical Strategies
--------------------
RSI Strategy
~~~~~~~~~~~
.. automodule:: src.strategies.technical.rsi_strategy
:members:
:undoc-members:
:show-inheritance:
MACD Strategy
~~~~~~~~~~~~~
.. automodule:: src.strategies.technical.macd_strategy
:members:
:undoc-members:
:show-inheritance:
Moving Average Strategy
~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.technical.moving_avg_strategy
:members:
:undoc-members:
:show-inheritance:
DCA Strategy
~~~~~~~~~~~~
.. automodule:: src.strategies.dca.dca_strategy
:members:
:undoc-members:
:show-inheritance:
Grid Strategy
~~~~~~~~~~~~~
.. automodule:: src.strategies.grid.grid_strategy
:members:
:undoc-members:
:show-inheritance:
Momentum Strategy
~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.momentum.momentum_strategy
:members:
:undoc-members:
:show-inheritance:
Confirmed Strategy
~~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.technical.confirmed_strategy
:members:
:undoc-members:
:show-inheritance:
Divergence Strategy
~~~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.technical.divergence_strategy
:members:
:undoc-members:
:show-inheritance:
Bollinger Mean Reversion Strategy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.technical.bollinger_mean_reversion
:members:
:undoc-members:
:show-inheritance:
Consensus Strategy (Ensemble)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: src.strategies.ensemble.consensus_strategy
:members:
:undoc-members:
:show-inheritance:
Timeframe Manager
-----------------
.. automodule:: src.strategies.timeframe_manager
:members:
:undoc-members:
:show-inheritance:
Strategy Scheduler
----------------
.. automodule:: src.strategies.scheduler
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,45 @@
Trading Engine
==============
The trading engine handles order execution, position management, and exchange interactions.
Trading Engine
--------------
.. automodule:: src.trading.engine
:members:
:undoc-members:
:show-inheritance:
Order Manager
-------------
.. automodule:: src.trading.order_manager
:members:
:undoc-members:
:show-inheritance:
Paper Trading
-------------
.. automodule:: src.trading.paper_trading
:members:
:undoc-members:
:show-inheritance:
Advanced Orders
---------------
.. automodule:: src.trading.advanced_orders
:members:
:undoc-members:
:show-inheritance:
Futures Trading
---------------
.. automodule:: src.trading.futures
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,378 @@
# Autopilot Architecture
This document describes the architecture of the Autopilot system, including both Pattern-Based and ML-Based modes.
## Overview
The Autopilot system provides autonomous trading signal generation using an **Intelligent Autopilot** powered by machine learning-based strategy selection.
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Unified Autopilot API │
│ (/api/autopilot/start-unified) │
└───────────────────────┬─────────────────────────────────────┘
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ ML-Based │ │ Signal Display │
│ Autopilot │ │ (Dashboard) │
├───────────────────┤ └───────────────────┘
│ MarketAnalyzer │
│ StrategySelector │
│ PerformanceTracker│
└───────────────────┘
┌───────────────────────┐
│ Signal Generation │
│ (StrategySignal) │
└───────────────────────┘
┌───────────────────┐
│ Auto-Execution │
│ (Optional) │
└───────────────────┘
```
## ML-Based Autopilot Flow
> [!NOTE]
> **Global Model Architecture**: The system uses a single "Global Model" trained on data from *all* configured symbols. This allows for shared learning (patterns from BTC help predict ETH) and efficient resource usage.
> [!IMPORTANT]
> **Single-Asset Focus**: The current ML model is designed for **single-asset strategies** (e.g., Momentum, RSI, Grid). It analyzes each coin in isolation. It does **NOT** currently support multi-asset strategies like **Arbitrage**, which require analyzing correlations between multiple coins simultaneously.
```
Market Data (OHLCV)
┌───────────────────┐
│ MarketAnalyzer │
│ - Analyze │
│ conditions │
│ - Determine │
│ regime │
└─────────┬─────────┘
┌───────────────────┐
│ StrategySelector │
│ - Global ML Model │
│ - Select best │
│ strategy │
│ - Calculate │
│ confidence │
└─────────┬─────────┘
┌───────────────────┐
│ Selected Strategy │
│ - Generate signal│
│ - Calculate size │
└─────────┬─────────┘
StrategySignal
```
## API Endpoints
### Unified Endpoints
- `POST /api/autopilot/start-unified` - Start autopilot
- `POST /api/autopilot/stop-unified` - Stop autopilot
- `GET /api/autopilot/status-unified/{symbol}` - Get autopilot status
### Legacy Endpoints (Removed)
- `POST /api/autopilot/start` - (Removed)
- `POST /api/autopilot/intelligent/start` - (Removed)
## Mode Selection Logic
The unified endpoints now exclusively support the "intelligent" mode. Code handling other modes has been removed.
```python
def start_unified_autopilot(config: UnifiedAutopilotConfig):
# Always starts intelligent autopilot
autopilot = get_intelligent_autopilot(
symbol=config.symbol,
exchange_id=config.exchange_id,
timeframe=config.timeframe,
interval=config.interval,
paper_trading=config.paper_trading
)
if config.auto_execute:
autopilot.enable_auto_execution = True
autopilot.start()
```
## Auto-Execution Integration
Auto-execution is integrated directly into the ML-based workflow:
```python
# Auto-Execution
if auto_execute and signal and confidence > threshold:
trading_engine.execute_order(
symbol=signal.symbol,
side=signal.signal_type,
quantity=strategy.calculate_position_size(signal, balance),
...
)
```
## Pre-Flight Order Validation
Before submitting orders, the autopilot validates that the order will succeed:
```python
async def _can_execute_order(side, quantity, price) -> Tuple[bool, str]:
# 1. Check minimum order value ($1 USD)
if order_value < 1.0:
return False, "Order value below minimum"
# 2. For BUY: check sufficient balance (with fee buffer)
if side == BUY:
if balance < (order_value + fee_estimate):
return False, "Insufficient funds"
# 3. For SELL: check position exists
if side == SELL:
if no_position:
return False, "No position to sell"
return True, "OK"
```
**Benefits:**
- Prevents creation of PENDING orders that would be REJECTED
- Cleaner order history (no garbage orders)
- Faster execution (skip validation at execution layer)
## Smart Order Type Selection
The autopilot intelligently chooses between LIMIT and MARKET orders:
| Scenario | Order Type | Rationale |
|----------|-----------|-----------|
| Strong signal (>80%) | MARKET | High confidence, execute now |
| Normal BUY signal | LIMIT (-0.1%) | Get better entry, pay maker fees |
| Take-profit SELL | LIMIT (+0.1%) | Get better exit price |
| Stop-loss SELL | MARKET | Urgent exit, protect capital |
```python
def _determine_order_type_and_price(side, signal_strength, price, is_stop_loss):
# Strong signals or stop-losses use MARKET
if signal_strength > 0.8 or is_stop_loss:
return MARKET, None
# Normal signals use LIMIT with 0.1% better price
if side == BUY:
limit_price = price * 0.999 # 0.1% below market
else:
limit_price = price * 1.001 # 0.1% above market
return LIMIT, limit_price
```
**Stop-Loss vs Take-Profit Detection:**
- If `current_price < entry_price`: Stop-loss (use MARKET)
- If `current_price > entry_price`: Take-profit (use LIMIT)
## Data Flow
1. **Market Data Collection**: OHLCV data from database
2. **Market Analysis**: MarketAnalyzer determines conditions
3. **Strategy Selection**: ML model selects best strategy
4. **Signal Generation**: Selected strategy generates signal
5. **Opportunity Evaluation**: Check confidence and criteria
6. **Execution** (if enabled): Execute trade
7. **Learning**: Record trade for model improvement
## Component Responsibilities
- **MarketAnalyzer**: Analyzes market conditions and determines regime
- **StrategySelector**: ML model for selecting optimal strategy
- **PerformanceTracker**: Tracks strategy performance for learning
- **IntelligentAutopilot**: Orchestrates ML-based selection and execution
## Configuration
```python
{
"symbol": "BTC/USD",
"exchange_id": 1,
"timeframe": "1h",
"interval": 60.0, # Analysis cycle in seconds
"paper_trading": True,
"min_confidence_threshold": 0.75,
"max_trades_per_day": 10,
"auto_execute": False
}
```
## State Management
- `_running`: Boolean flag
- `_last_analysis`: Last analysis result
- `_selected_strategy`: Currently selected strategy
- `_trades_today`: Trade count for daily limit
- `_ohlcv_data`: Current market data
- `_strategy_instances`: Cached strategy instances
- `_intelligent_autopilots`: Global registry of active instances
## Error Handling
Both modes implement error handling:
- **Market Data Errors**: Fallback to cached data or skip cycle
- **Pattern Detection Errors**: Return no pattern, continue monitoring
- **Sentiment Analysis Errors**: Use neutral sentiment, continue
- **ML Model Errors**: Fallback to rule-based selection
- **Execution Errors**: Log error, continue monitoring
## Performance Considerations
- **Moderate**: ML model adds overhead but is optimized for speed
- **Adaptive**: Performance improves with more training data
- **Scalable**: Model can handle multiple symbols with shared learning
## ML Model Training
The ML model training system supports background training via Celery with configurable parameters.
### Training Configuration
Training is configured via the Bootstrap Config API and persisted in the application config:
| Parameter | Description | Default |
|-----------|-------------|---------|
| `days` | Historical data (days) to fetch | 90 |
| `timeframe` | OHLCV candle timeframe | `1h` |
| `min_samples_per_strategy` | Minimum samples needed per strategy | 10 |
| `symbols` | Cryptocurrencies to train on | `["BTC/USD", "ETH/USD"]` |
### API Endpoints
```
GET /api/autopilot/bootstrap-config # Get current config
PUT /api/autopilot/bootstrap-config # Update config
POST /api/autopilot/intelligent/retrain # Trigger retraining
GET /api/autopilot/tasks/{task_id} # Poll training status
GET /api/autopilot/intelligent/model-info # Get model info
POST /api/autopilot/intelligent/reset # Reset model
```
### Background Training Architecture
```
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Settings Page │────▶│ FastAPI │────▶│ Celery Worker │
│ (React) │ │ Backend │ │ (train_task) │
└─────────────────┘ └──────────────┘ └─────────────────┘
│ │ │
│ Poll status │ get_config() │ Reset singletons
│◀─────────────────────│ │ Bootstrap data
│ │ │ Train model
│ │ │ Save to disk
│ │◀─────────────────────│
│ SUCCESS + metrics │ │
│◀─────────────────────│ │
│ │ │
▼ ▼ ▼
UI shows results Model auto-loads Model file saved
```
### Training Task Flow
1. **Singleton Reset**: Reset `StrategySelector`, `PerformanceTracker`, and `Database` singletons to prevent async conflicts
2. **Check Existing Data**: Query performance tracker for existing training samples
3. **Bootstrap (if needed)**: Fetch OHLCV data for each symbol with progress updates
4. **Train Model**: Train ML model with cross-validation and walk-forward validation
5. **Save Model**: Persist trained model to `~/.local/share/crypto_trader/models/`
6. **Return Metrics**: Return training accuracy and model metadata
### Model Auto-Reload
The `StrategySelector.get_model_info()` method automatically checks for new model files:
```python
def get_model_info(self):
# Always check if a newer model is available on disk
self._try_load_saved_model() # Compare timestamps, reload if newer
return {...}
```
This ensures the API process picks up newly trained models without restart.
### Error Handling
- **Celery Task Errors**: Serialized to JSON with traceback for frontend display
- **Polling Errors**: Frontend stops polling after 3 consecutive failures
- **Connection Errors**: Singletons reset to prevent `asyncpg.InterfaceError`
## Strategy Grouping
To improve ML accuracy, individual strategies are grouped into 5 logical categories. The model predicts the best **group**, then rule-based logic selects the optimal strategy within that group.
### Strategy Groups
| Group | Strategies | Market Conditions |
|-------|-----------|-------------------|
| **Trend Following** | moving_average, macd, confirmed | Strong trends (ADX > 25) |
| **Mean Reversion** | rsi, bollinger_mean_reversion, grid, divergence | Ranging markets, oversold/overbought |
| **Momentum** | momentum, volatility_breakout | High volume spikes, breakouts |
| **Market Making** | market_making, dca | Low volatility, stable markets |
| **Sentiment Based** | sentiment, pairs_trading, consensus | External signals available |
### Benefits
- **Reduced Classes**: 5 groups vs 14 strategies (~20% random baseline vs ~7%)
- **Better Generalization**: Model learns group characteristics rather than memorizing individual strategies
- **Combined Confidence**: `group_confidence × strategy_confidence` for final score
## Improved Bootstrap Sampling
Training data is collected using intelligent sampling rather than fixed intervals:
### Sampling Strategies
1. **Regime-Change Detection**: Sample when market regime changes (e.g., trending → ranging)
2. **Minimum Time Spacing**: 24-hour gaps between samples for 1h timeframe
3. **Periodic Sampling**: Every 48 hours regardless of regime changes
### Timeframe Spacing
| Timeframe | Min Spacing (candles) | Actual Time |
|-----------|----------------------|-------------|
| 1m | 1440 | 24 hours |
| 15m | 96 | 24 hours |
| 1h | 24 | 24 hours |
| 4h | 6 | 24 hours |
| 1d | 1 | 24 hours |
## Security Considerations
- **Auto-Execution**: Requires explicit user confirmation
- **Paper Trading**: Default mode for safety
- **Risk Limits**: Enforced regardless of mode
- **API Keys**: Encrypted storage
- **Audit Logging**: All autopilot actions logged
## Future Enhancements
- Hybrid mode combining both approaches
- Real-time mode switching
- Multi-symbol autopilot management
- Advanced risk management integration
- Performance analytics dashboard
- Persistent training configuration (file/database storage)

View File

@@ -0,0 +1,135 @@
# Backtesting Engine Architecture
This document describes the backtesting engine design.
## Backtesting Components
```
Backtesting Engine
├──► Data Provider
│ │
│ └──► Historical Data Loading
├──► Strategy Execution
│ │
│ ├──► Data Replay
│ ├──► Signal Generation
│ └──► Order Simulation
├──► Realism Models
│ │
│ ├──► Slippage Model
│ ├──► Fee Model
│ └──► Order Book Simulation
└──► Metrics Calculation
├──► Performance Metrics
└──► Risk Metrics
```
## Data Replay
Historical data is replayed chronologically:
```
Historical Data (Time Series)
Time-based Iteration
├──► For each timestamp:
│ │
│ ├──► Update market data
│ ├──► Notify strategies
│ ├──► Process signals
│ └──► Execute orders
└──► Continue until end date
```
## Order Simulation
Simulated order execution:
```
Order Request
Order Type Check
├──► Market Order
│ │
│ └──► Execute at current price + slippage
└──► Limit Order
└──► Wait for price to reach limit
└──► Execute when filled
```
## Slippage Modeling
Realistic slippage simulation:
```
Market Order
Current Price
├──► Buy Order: Price + Slippage
└──► Sell Order: Price - Slippage
└──► Add Market Impact (for large orders)
```
## Fee Modeling
Exchange fee calculation:
```
Order Execution
Order Type Check
├──► Maker Order (Limit)
│ │
│ └──► Apply Maker Fee
└──► Taker Order (Market)
└──► Apply Taker Fee
```
## Performance Metrics
Calculated metrics:
- **Total Return**: (Final Capital - Initial Capital) / Initial Capital
- **Sharpe Ratio**: (Return - Risk-free Rate) / Volatility
- **Sortino Ratio**: (Return - Risk-free Rate) / Downside Deviation
- **Max Drawdown**: Largest peak-to-trough decline
- **Win Rate**: Winning Trades / Total Trades
- **Profit Factor**: Gross Profit / Gross Loss
## Parameter Optimization
Optimization methods:
- **Grid Search**: Test all parameter combinations
- **Genetic Algorithm**: Evolutionary optimization
- **Bayesian Optimization**: Efficient parameter search
## Backtest Results
Stored results:
- Performance metrics
- Trade history
- Equity curve
- Drawdown chart
- Parameter values

View File

@@ -0,0 +1,233 @@
# Data Flow Architecture
This document describes how data flows through the Crypto Trader system.
## Real-Time Data Flow
```
Exchange WebSocket
Exchange Adapter
Data Collector
├──► Data Quality Validation
├──► Data Storage (Database)
└──► Timeframe Manager
├──► Strategy 1 (1h timeframe)
├──► Strategy 2 (15m timeframe)
└──► Strategy 3 (1d timeframe)
Signal Generation
Trading Engine
```
## Trading Signal Flow
```
Market Data Update
Strategy.on_data()
Indicator Calculation
Signal Generation
Trading Engine
├──► Risk Manager (Pre-trade Check)
│ │
│ ├──► Position Sizing
│ ├──► Drawdown Check
│ └──► Daily Loss Check
Order Manager
├──► Paper Trading (if enabled)
│ │
│ └──► Paper Trading Simulator
└──► Live Trading
Exchange Adapter
Exchange API
Order Execution
Order Manager (Update Status)
Position Tracker
Portfolio Analytics
```
## Backtesting Data Flow
```
Historical Data Provider
Backtesting Engine
├──► Data Replay (Time-based)
├──► Strategy Execution
│ │
│ ├──► Signal Generation
│ │
│ └──► Order Simulation
│ │
│ ├──► Slippage Model
│ ├──► Fee Model
│ └──► Order Book Simulation
└──► Performance Calculation
├──► Metrics Calculation
│ │
│ ├──► Returns
│ ├──► Sharpe Ratio
│ ├──► Sortino Ratio
│ └──► Drawdown
└──► Results Storage
```
## Portfolio Update Flow
```
Trade Execution
Position Update
├──► Position Tracker
│ │
│ ├──► Update Quantity
│ ├──► Update Entry Price
│ └──► Calculate P&L
└──► Portfolio Analytics
├──► Recalculate Metrics
│ │
│ ├──► Total Value
│ ├──► Unrealized P&L
│ ├──► Realized P&L
│ └──► Performance Metrics
└──► Alert System
└──► Risk Alerts (if triggered)
```
## Data Storage Flow
```
Data Collection
Data Quality Check
├──► Valid Data
│ │
│ └──► Database Storage
│ │
│ ├──► Market Data Table
│ ├──► Trades Table
│ └──► Positions Table
└──► Invalid Data
└──► Error Logging
└──► Gap Detection
└──► Gap Filling (if enabled)
```
## Multi-Timeframe Synchronization
```
1m Data Stream
├──► Strategy (1m)
└──► Resample to 5m
├──► Strategy (5m)
└──► Resample to 1h
├──► Strategy (1h)
└──► Resample to 1d
└──► Strategy (1d)
```
## Alert Flow
```
Data Update / Event
Alert Engine
├──► Check Alert Conditions
│ │
│ ├──► Price Alerts
│ ├──► Indicator Alerts
│ ├──► Risk Alerts
│ └──► System Alerts
└──► Trigger Alert (if condition met)
├──► Desktop Notification
├──► Sound Alert
└──► Email Notification (if configured)
```
## Error Recovery Flow
```
Error Detected
Error Recovery System
├──► Log Error
├──► Attempt Recovery
│ │
│ ├──► Retry Operation
│ ├──► Fallback Mechanism
│ └──► State Restoration
└──► Health Monitor
├──► Check System Health
└──► Auto-restart (if critical)
```

View File

@@ -0,0 +1,288 @@
# Database Schema
This document describes the database schema for Crypto Trader.
## Database Options
- **SQLite**: Default, bundled, zero configuration
- **PostgreSQL**: Optional, for advanced users with large datasets
## Core Tables
### Exchanges
Stores exchange configuration and credentials.
```sql
CREATE TABLE exchanges (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
api_key TEXT, -- Encrypted
secret_key TEXT, -- Encrypted
password TEXT, -- Encrypted (for some exchanges)
api_permissions TEXT DEFAULT 'read_only',
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
```
### Strategies
Stores trading strategy configuration.
```sql
CREATE TABLE strategies (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT,
strategy_type TEXT NOT NULL,
parameters TEXT, -- JSON
is_enabled BOOLEAN DEFAULT FALSE,
is_paper_trading BOOLEAN DEFAULT TRUE,
exchange_id INTEGER REFERENCES exchanges(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
```
### Trades
Stores executed trades.
```sql
CREATE TABLE trades (
id INTEGER PRIMARY KEY,
order_id TEXT UNIQUE NOT NULL,
symbol TEXT NOT NULL,
side TEXT NOT NULL, -- 'buy' or 'sell'
type TEXT NOT NULL, -- 'market', 'limit', etc.
price REAL,
amount REAL,
cost REAL,
fee REAL DEFAULT 0.0,
status TEXT DEFAULT 'open',
is_paper_trade BOOLEAN DEFAULT TRUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
exchange_id INTEGER REFERENCES exchanges(id),
strategy_id INTEGER REFERENCES strategies(id)
);
```
### Positions
Stores open and closed positions.
```sql
CREATE TABLE positions (
id INTEGER PRIMARY KEY,
symbol TEXT NOT NULL,
exchange_id INTEGER REFERENCES exchanges(id),
quantity REAL NOT NULL,
entry_price REAL NOT NULL,
current_price REAL,
unrealized_pnl REAL,
realized_pnl REAL,
is_open BOOLEAN DEFAULT TRUE,
position_type TEXT DEFAULT 'spot', -- 'spot', 'futures', 'margin'
leverage REAL DEFAULT 1.0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
UNIQUE(symbol, exchange_id, position_type)
);
```
### Orders
Stores order history and status.
```sql
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
exchange_order_id TEXT UNIQUE NOT NULL,
client_order_id TEXT,
symbol TEXT NOT NULL,
side TEXT NOT NULL,
type TEXT NOT NULL,
price REAL,
amount REAL,
filled_amount REAL DEFAULT 0.0,
remaining_amount REAL DEFAULT 0.0,
cost REAL DEFAULT 0.0,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
exchange_id INTEGER REFERENCES exchanges(id),
strategy_id INTEGER REFERENCES strategies(id),
is_paper_trade BOOLEAN DEFAULT TRUE
);
```
### Market Data
Stores historical OHLCV data.
```sql
CREATE TABLE market_data (
id INTEGER PRIMARY KEY,
symbol TEXT NOT NULL,
timeframe TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
open REAL NOT NULL,
high REAL NOT NULL,
low REAL NOT NULL,
close REAL NOT NULL,
volume REAL NOT NULL,
exchange_id INTEGER REFERENCES exchanges(id),
UNIQUE(symbol, timeframe, timestamp)
);
```
### Portfolio Snapshots
Stores portfolio snapshots over time.
```sql
CREATE TABLE portfolio_snapshots (
id INTEGER PRIMARY KEY,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_value REAL NOT NULL,
cash_balance REAL NOT NULL,
asset_holdings TEXT, -- JSON
exchange_id INTEGER REFERENCES exchanges(id)
);
```
### Backtest Results
Stores backtesting results.
```sql
CREATE TABLE backtest_results (
id INTEGER PRIMARY KEY,
strategy_id INTEGER REFERENCES strategies(id),
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
initial_capital REAL NOT NULL,
final_capital REAL NOT NULL,
total_return REAL NOT NULL,
sharpe_ratio REAL,
sortino_ratio REAL,
max_drawdown REAL,
win_rate REAL,
other_metrics TEXT, -- JSON
run_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### Risk Limits
Stores risk management limits.
```sql
CREATE TABLE risk_limits (
id INTEGER PRIMARY KEY,
strategy_id INTEGER REFERENCES strategies(id),
exchange_id INTEGER REFERENCES exchanges(id),
limit_type TEXT NOT NULL,
value REAL NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
```
### Alerts
Stores alert configurations.
```sql
CREATE TABLE alerts (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
alert_type TEXT NOT NULL,
condition TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
triggered_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### Rebalancing Events
Stores portfolio rebalancing history.
```sql
CREATE TABLE rebalancing_events (
id INTEGER PRIMARY KEY,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
exchange_id INTEGER REFERENCES exchanges(id),
old_allocations TEXT, -- JSON
new_allocations TEXT, -- JSON
executed_trades TEXT, -- JSON
status TEXT DEFAULT 'completed'
);
```
### App State
Stores application state for recovery.
```sql
CREATE TABLE app_state (
id INTEGER PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
value TEXT, -- JSON
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### Audit Log
Stores security audit events.
```sql
CREATE TABLE audit_log (
id INTEGER PRIMARY KEY,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
event_type TEXT NOT NULL,
user_id TEXT,
details TEXT -- JSON
);
```
## Indexes
Key indexes for performance:
- `market_data(symbol, timeframe, timestamp)` - Unique index
- `trades(symbol, executed_at)` - For trade queries
- `positions(symbol, exchange_id, is_open)` - For position lookups
- `orders(status, created_at)` - For order management
- `strategies(is_enabled)` - For active strategy queries
## Relationships
- **Exchanges** → **Strategies** (one-to-many)
- **Exchanges** → **Trades** (one-to-many)
- **Strategies** → **Trades** (one-to-many)
- **Strategies** → **Backtest Results** (one-to-many)
- **Exchanges** → **Positions** (one-to-many)
- **Exchanges** → **Market Data** (one-to-many)
## Data Retention
Configurable retention policies:
- **Market Data**: Configurable (default: 1 year)
- **Trades**: Permanent
- **Orders**: Permanent
- **Portfolio Snapshots**: Configurable (default: 1 year)
- **Logs**: Configurable (default: 30 days)
## Backup and Recovery
- **Automatic Backups**: Before critical operations
- **Manual Backups**: Via export functionality
- **Recovery**: From backup files or database dumps

View File

@@ -0,0 +1,176 @@
# Exchange Integration Architecture
This document describes how exchange adapters integrate with the trading system.
## Adapter Pattern
All exchanges use the adapter pattern to provide a unified interface:
```
Trading Engine
BaseExchange (Interface)
├──► CoinbaseExchange
├──► BinanceExchange (future)
└──► KrakenExchange (future)
```
## Base Exchange Interface
All exchanges implement `BaseExchange`:
```python
class BaseExchange(ABC):
async def connect()
async def disconnect()
async def fetch_balance()
async def place_order()
async def cancel_order()
async def fetch_order_status()
async def fetch_ohlcv()
async def subscribe_ohlcv()
async def subscribe_trades()
async def subscribe_order_book()
async def fetch_open_orders()
async def fetch_positions()
async def fetch_markets()
```
## Exchange Factory
The factory pattern creates exchange instances:
```
ExchangeFactory
├──► get_exchange(name)
│ │
│ ├──► Lookup registered adapter
│ ├──► Get API keys from KeyManager
│ └──► Instantiate adapter
└──► register_exchange(name, adapter_class)
```
## Exchange Registration
Exchanges are registered at module import:
```python
# In exchange module
from src.exchanges.factory import ExchangeFactory
ExchangeFactory.register_exchange("coinbase", CoinbaseExchange)
```
## CCXT Integration
Most exchanges use the CCXT library:
```python
import ccxt.pro as ccxt
class CoinbaseExchange(BaseExchange):
def __init__(self, ...):
self.exchange = ccxt.coinbaseadvanced({
'apiKey': api_key,
'secret': secret_key,
'enableRateLimit': True,
})
```
## WebSocket Support
Real-time data via WebSockets:
```python
async def subscribe_ohlcv(self, symbol, timeframe, callback):
"""Subscribe to OHLCV updates."""
await self.exchange.watch_ohlcv(symbol, timeframe, callback)
```
## Rate Limiting
All exchanges respect rate limits:
- CCXT handles rate limiting automatically
- `enableRateLimit: True` in exchange config
- Custom rate limiting for non-CCXT exchanges
## Error Handling
Exchange-specific error handling:
```python
try:
order = await self.exchange.create_order(...)
except ccxt.NetworkError as e:
# Handle network errors
logger.error(f"Network error: {e}")
raise
except ccxt.ExchangeError as e:
# Handle exchange errors
logger.error(f"Exchange error: {e}")
raise
```
## Connection Management
- **Connection Pooling**: Reuse connections when possible
- **Auto-Reconnect**: Automatic reconnection on disconnect
- **Health Monitoring**: Monitor connection health
- **Graceful Shutdown**: Properly close connections
## Adding New Exchanges
See [Adding Exchanges](../developer/adding_exchanges.md) for detailed guide.
## Exchange-Specific Features
Some exchanges have unique features:
- **Coinbase**: Requires passphrase for some operations
- **Binance**: Futures and margin trading
- **Kraken**: Different order types
These are handled in exchange-specific adapters.
## WebSocket Implementation
### Coinbase WebSocket Support
The Coinbase adapter includes WebSocket subscription methods:
- **subscribe_ticker()**: Subscribe to real-time price updates
- **subscribe_orderbook()**: Subscribe to order book changes
- **subscribe_trades()**: Subscribe to trade executions
### WebSocket Architecture
```
Exchange WebSocket API
CoinbaseAdapter.subscribe_*()
Callback Functions
DataCollector (with signals)
UI Widgets (via signals)
```
### Reconnection Strategy
- Automatic reconnection on disconnect
- Message queuing during disconnection
- Heartbeat/ping-pong for connection health
- Fallback to polling if WebSocket unavailable
### Implementation Notes
- Uses `websockets` library for async WebSocket connections
- Callbacks are wrapped to emit Qt signals for UI updates
- Basic implementation provided; can be extended for full Coinbase Advanced Trade WebSocket API

View File

@@ -0,0 +1,185 @@
# Architecture Overview
This document provides a high-level overview of the Crypto Trader architecture.
## System Architecture
Crypto Trader follows a modular architecture with clear separation of concerns:
```
┌─────────────────────────────────────────────────────────┐
│ Frontend (React + Vite) │
│ Dashboard | Strategies | Portfolio | Backtest | Settings│
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Backend API (FastAPI) │
│ Autopilot | Trading | Market Data | WebSocket │
└─────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Redis │ │ Celery │ │ PostgreSQL │
│ State/Cache │←───│ Workers │ │ Database │
└──────────────┘ └──────────────┘ └──────────────────┘
▲ │
│ ▼
┌─────────────────────────────────────────────────────────┐
│ Trading Engine │
│ Order Management | Position Tracking | Risk Management │
└─────────────────────────────────────────────────────────┘
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Strategies │ │ Exchanges │ │ Portfolio │
│ Framework │ │ Adapters │ │ Tracker │
└──────────────┘ └──────────────┘ └──────────────┘
```
## Core Components
### 1. Frontend Layer
- **Framework**: React with TypeScript
- **Build Tool**: Vite
- **Components**: Dashboard, Strategy Manager, Portfolio View, Backtest View, Settings
- **State Management**: React Query, Context API
- **Styling**: Material-UI (MUI)
### 2. Backend API
- **Framework**: FastAPI (async Python)
- **Features**: RESTful API, WebSocket support, auto-docs (Swagger/OpenAPI)
- **Authentication**: JWT tokens (planned)
- **Responsibilities**: Orchestrates all business logic
### 3. Redis (State Management)
- **Purpose**: Distributed state management and caching
- **Use Cases**:
- Autopilot registry (prevents multiple instances)
- Daily trade count persistence (survives restarts)
- Session caching (planned)
- Real-time data caching (planned)
- **Configuration**: `src/core/config.py``redis` section
### 4. Celery (Background Tasks)
- **Purpose**: Offload CPU-intensive tasks from the API
- **Use Cases**:
- ML model training (`train_model_task`)
- Data bootstrapping (`bootstrap_task`)
- Report generation (`generate_report_task`)
- **Broker**: Redis
- **Worker Command**: `celery -A src.worker.app worker --loglevel=info`
### 5. Trading Engine
- **Responsibilities**: Order execution, position management, risk checks
- **Components**: Trading engine, order manager, paper trading simulator
- **Integration**: Connects strategies to exchanges
### 6. Strategy Framework
- **Base Class**: `BaseStrategy` provides common interface
- **Registry**: Manages available strategies (RSI, MACD, Bollinger, etc.)
- **ML Selection**: `IntelligentAutopilot` uses ML to select optimal strategies
- **Features**: Multi-timeframe support, scheduling, parameter management
### 7. Exchange Adapters
- **Pattern**: Adapter pattern for unified interface
- **Factory**: Dynamic exchange instantiation
- **Current**: Coinbase Advanced Trade API, Binance Public
- **Data Providers**: CCXT-based providers with automatic failover
### 8. Risk Management
- **Components**: Risk manager, stop-loss, position sizing, limits
- **Integration**: Pre-trade checks, real-time monitoring
- **Features**: Drawdown limits, daily loss limits, position limits
### 9. Backtesting Engine
- **Features**: Historical data replay, realistic simulation
- **Components**: Engine, metrics, slippage model, fee model
- **Optimization**: Parameter optimization support
### 10. Portfolio Management
- **Tracking**: Real-time position tracking
- **Analytics**: Performance metrics, risk analysis
- **Rebalancing**: Automatic portfolio rebalancing (planned)
## Data Flow
### Trading Flow
1. User starts autopilot → API receives request
2. Redis lock checked/set → Prevents duplicate instances
3. Strategy generates signal
4. Risk manager validates trade
5. Order manager creates order
6. Exchange adapter executes order
7. Position tracker updates positions
8. Redis updates trade count
### ML Training Flow (Background)
1. User triggers `/retrain` API
2. API queues `train_model_task` to Celery
3. API returns task ID immediately (non-blocking)
4. Celery worker picks up task
5. Worker bootstraps data if needed
6. Worker trains model
7. User polls `/tasks/{task_id}` for status
## Technology Stack
- **Language**: Python 3.11+
- **Frontend**: React 18, TypeScript, Vite
- **Backend**: FastAPI, Uvicorn
- **Database**: PostgreSQL with SQLAlchemy
- **Cache/State**: Redis
- **Task Queue**: Celery
- **Exchange Library**: CCXT
- **Data Analysis**: Pandas, NumPy
- **Machine Learning**: LightGBM, scikit-learn
- **Technical Analysis**: pandas-ta, TA-Lib
- **Async**: asyncio
- **Testing**: pytest, React Testing Library
## Design Patterns
- **Adapter Pattern**: Exchange adapters
- **Factory Pattern**: Exchange and strategy creation
- **Strategy Pattern**: Trading strategies
- **Observer Pattern**: Data updates to strategies
- **Singleton Pattern**: Configuration, database connections, Redis client
## Security Architecture
- **API Key Encryption**: Fernet encryption
- **Secure Storage**: Keyring integration
- **Audit Logging**: All security events logged
- **Permission Management**: Read-only vs trading modes
## Scalability Considerations
- **Async Operations**: Non-blocking I/O throughout
- **Redis State**: Enables horizontal scaling of API workers
- **Celery Workers**: Can scale independently for heavy workloads
- **Database Optimization**: Indexed queries, connection pooling
- **Data Retention**: Configurable retention policies
## Extension Points
- **Exchange Adapters**: Add new exchanges via adapter interface
- **Strategies**: Create custom strategies via base class
- **Indicators**: Add custom indicators
- **Order Types**: Extend advanced order types
- **Risk Rules**: Add custom risk management rules
- **Celery Tasks**: Add new background tasks in `src/worker/tasks.py`

View File

@@ -0,0 +1,165 @@
# Risk Management Architecture
This document describes the risk management system.
## Risk Management Components
```
Risk Manager
├──► Pre-Trade Checks
│ │
│ ├──► Position Sizing
│ ├──► Daily Loss Limit
│ └──► Portfolio Allocation
├──► Real-Time Monitoring
│ │
│ ├──► Drawdown Monitoring
│ ├──► Position Monitoring
│ └──► Portfolio Monitoring
└──► Stop Loss Management
├──► Stop Loss Orders
└──► Trailing Stops
```
## Pre-Trade Risk Checks
Before executing any trade:
1. **Position Sizing Check**
- Verify position size within limits
- Check portfolio allocation
- Validate against risk parameters
2. **Daily Loss Limit Check**
- Calculate current daily P&L
- Compare against daily loss limit
- Block trades if limit exceeded
3. **Drawdown Check**
- Calculate current drawdown
- Compare against max drawdown limit
- Block trades if limit exceeded
4. **Portfolio Allocation Check**
- Verify total exposure within limits
- Check per-asset allocation
- Validate diversification requirements
## Position Sizing Methods
### Fixed Percentage
```python
position_size = capital * percentage
```
### Kelly Criterion
```python
f = (bp - q) / b
position_size = capital * f
```
### Volatility-Based
```python
position_size = (capital * risk_percentage) / (stop_loss_distance * price)
```
## Risk Limits
Configurable limits:
- **Max Drawdown**: Maximum allowed drawdown percentage
- **Daily Loss Limit**: Maximum daily loss percentage
- **Position Size Limit**: Maximum position value
- **Portfolio Exposure**: Maximum portfolio exposure percentage
## Stop Loss Management
### Stop Loss Types
- **Fixed Stop Loss**: Fixed price level
- **Trailing Stop**: Adjusts with price movement
- Percentage-based: Adjusts by fixed percentage
- ATR-based: Adjusts based on volatility (Average True Range)
- **Percentage Stop**: Percentage below entry
- **ATR-based Stop**: Based on Average True Range (volatility-adjusted)
- Automatically calculates stop distance using ATR multiplier
- Adapts to market volatility conditions
- Configurable ATR period (default: 14) and multiplier (default: 2.0)
- Works with both fixed and trailing stops
### ATR-Based Dynamic Stops
ATR-based stops provide better risk management in volatile markets:
```python
stop_loss_manager.set_stop_loss(
position_id=1,
stop_price=entry_price,
use_atr=True,
atr_multiplier=Decimal('2.0'),
atr_period=14,
ohlcv_data=market_data,
trailing=True
)
```
**Benefits:**
- Adapts to market volatility
- Tighter stops in low volatility, wider in high volatility
- Reduces stop-outs during normal market noise
- Better risk-adjusted returns
**Calculation:**
- Stop distance = ATR × multiplier
- For long positions: stop_price = entry_price - (ATR × multiplier)
- For short positions: stop_price = entry_price + (ATR × multiplier)
### Stop Loss Execution
```
Price Update
Stop Loss Check
├──► Stop Loss Triggered?
│ │
│ └──► Execute Market Sell
└──► Update Trailing Stop (if applicable)
```
## Real-Time Monitoring
Continuous monitoring of:
- Portfolio value
- Unrealized P&L
- Drawdown levels
- Position sizes
- Risk metrics
## Risk Alerts
Automatic alerts for:
- Drawdown threshold exceeded
- Daily loss limit reached
- Position size exceeded
- Portfolio exposure exceeded
## Integration Points
Risk management integrates with:
- **Trading Engine**: Pre-trade validation
- **Order Manager**: Position tracking
- **Portfolio Tracker**: Real-time monitoring
- **Alert System**: Risk alerts

View File

@@ -0,0 +1,135 @@
# Security Architecture
This document describes the security architecture of Crypto Trader.
## Security Layers
```
Application Layer
├──► API Key Encryption
├──► Permission Management
└──► Audit Logging
Storage Layer
├──► Encrypted Storage
└──► Secure Key Management
```
## API Key Encryption
### Encryption Process
```
Plain API Key
Fernet Encryption
Encrypted Key (Stored in Database)
```
### Key Management
- **Encryption Key**: Stored securely (environment variable or keyring)
- **Key Generation**: Automatic on first use
- **Key Rotation**: Manual rotation process
## Permission Management
### Permission Levels
- **Read-Only**: Data collection, backtesting only
- **Trading Enabled**: Full trading capabilities
### Permission Enforcement
```
API Request
Permission Check
├──► Read-Only Request
│ │
│ └──► Allow (read operations)
└──► Trading Request
├──► Trading Enabled?
│ │
│ ├──► Yes: Allow
│ └──► No: Reject
```
## Secure Storage
### Keyring Integration
- **Linux**: Secret Service (GNOME Keyring)
- **macOS**: Keychain
- **Windows**: Windows Credential Manager
### Fallback Storage
If keyring unavailable:
- Environment variable (development only)
- Encrypted file with user permission
## Audit Logging
All security events are logged:
- API key changes
- Permission changes
- Trading operations
- Configuration changes
- Error events
### Audit Log Format
```python
{
"timestamp": "2025-12-13T19:00:00Z",
"event_type": "API_KEY_CHANGED",
"user_id": "system",
"details": {
"exchange": "coinbase",
"action": "updated"
}
}
```
## Data Privacy
- **Local Storage**: All data stored locally
- **No Telemetry**: No data sent externally
- **Encryption**: Sensitive data encrypted at rest
- **Access Control**: File system permissions
## Best Practices
1. **Use Read-Only Keys**: When possible, use read-only API keys
2. **IP Whitelisting**: Enable IP whitelisting on exchange accounts
3. **Regular Rotation**: Rotate API keys periodically
4. **Secure Environment**: Keep encryption keys secure
5. **Audit Review**: Regularly review audit logs
## Threat Model
### Threats Addressed
- **API Key Theft**: Encryption at rest
- **Unauthorized Trading**: Permission checks
- **Data Breach**: Local storage, encryption
- **Man-in-the-Middle**: HTTPS for API calls
- **Key Logging**: Secure keyring storage
### Security Boundaries
- **Application Boundary**: Application code
- **Storage Boundary**: Encrypted database
- **Network Boundary**: Secure API connections
- **System Boundary**: File system permissions

View File

@@ -0,0 +1,208 @@
# Strategy Framework Architecture
This document describes the strategy framework design.
## Strategy Hierarchy
```
BaseStrategy (Abstract)
├──► Technical Strategies
│ ├──► RSIStrategy
│ ├──► MACDStrategy
│ ├──► MovingAverageStrategy
│ ├──► ConfirmedStrategy (Multi-Indicator)
│ ├──► DivergenceStrategy
│ └──► BollingerMeanReversionStrategy
├──► Ensemble Strategies
│ └──► ConsensusStrategy
├──► Other Strategies
│ ├──► DCAStrategy
│ ├──► GridStrategy
│ └──► MomentumStrategy
└──► CustomStrategy (user-defined)
```
## Base Strategy Interface
All strategies implement:
```python
class BaseStrategy(ABC):
async def on_data(new_data: pd.DataFrame)
async def generate_signal() -> Dict[str, Any]
async def calculate_position_size(capital, risk) -> float
async def start()
async def stop()
```
## Strategy Lifecycle
```
1. Initialization
└──► __init__(parameters)
2. Activation
└──► start()
3. Data Processing
└──► on_data(new_data)
└──► generate_signal()
└──► Trading Engine
4. Deactivation
└──► stop()
```
## Strategy Registry
Manages available strategies:
```python
StrategyRegistry
register_strategy(name, class)
get_strategy_class(name)
list_available()
```
## Multi-Timeframe Support
Strategies can use multiple timeframes:
```
Primary Timeframe (1h)
├──► Signal Generation
└──► Higher Timeframe (1d) - Trend Confirmation
└──► Lower Timeframe (15m) - Entry Timing
```
## Strategy Scheduling
Strategies can be scheduled:
- **Continuous**: Run on every new candle
- **Time-based**: Run at specific times
- **Condition-based**: Run when conditions met
## Signal Generation
Signal flow:
```
Data Update
Indicator Calculation
Strategy Logic
Signal Generation
├──► "buy" - Generate buy signal
├──► "sell" - Generate sell signal
└──► "hold" - No action
```
## Position Sizing
Strategies calculate position sizes:
- **Fixed Percentage**: Fixed % of capital
- **Kelly Criterion**: Optimal position sizing based on win rate
- **Volatility-Based**: Adjusts based on market volatility (ATR)
## Advanced Features
### Trend Filtering
All strategies can optionally use ADX-based trend filtering:
```python
signal = strategy.apply_trend_filter(
signal,
ohlcv_data,
adx_period=14,
min_adx=25.0
)
```
This filters out signals when:
- ADX < threshold (weak trend/chop)
- Signal direction doesn't match trend direction
### Multi-Indicator Confirmation
The ConfirmedStrategy requires multiple indicators to agree before generating signals, reducing false signals by 20-30%.
### Divergence Detection
Divergence strategies detect price vs. indicator divergences:
- Bullish divergence: Price lower low, indicator higher low → BUY
- Bearish divergence: Price higher high, indicator lower high → SELL
### Ensemble Methods
ConsensusStrategy aggregates signals from multiple strategies:
- Weighted voting by strategy performance
- Minimum consensus threshold
- Dynamic weighting based on recent performance
- **Kelly Criterion**: Optimal position sizing
- **Volatility-based**: Based on ATR
- **Risk-based**: Based on stop-loss distance
## Strategy Parameters
Configurable parameters:
- Strategy-specific parameters (e.g., RSI period)
- Risk parameters (position size, stop-loss)
- Timeframe settings
- Symbol selection
## Strategy Execution
Execution flow:
```
Strategy Signal
Trading Engine
├──► Risk Check
├──► Position Sizing
└──► Order Execution
├──► Paper Trading
└──► Live Trading
```
## Strategy Performance
Performance tracking:
- Win rate
- Total return
- Sharpe ratio
- Max drawdown
- Number of trades
## Extensibility
Easy to add new strategies:
1. Inherit from `BaseStrategy`
2. Implement required methods
3. Register with `StrategyRegistry`
4. Configure and use

View File

@@ -0,0 +1,270 @@
# UI Architecture
This document describes the user interface architecture and how the frontend integrates with the backend API.
## Overview
The UI is built with React and TypeScript, using Material-UI for components. The frontend communicates with the FastAPI backend through REST API endpoints and WebSocket connections for real-time updates.
## Architecture
```
React Frontend → REST API / WebSocket → FastAPI Backend → Python Services → Database
```
## Frontend Structure
```
frontend/src/
├── pages/ # Page components (Trading, Portfolio, Strategies, etc.)
├── components/ # Reusable UI components
├── api/ # API client functions
├── hooks/ # Custom React hooks
└── types/ # TypeScript type definitions
```
## Page Components
### DashboardPage
- Overview of trading activity
- Key metrics and charts
- Quick actions
### TradingPage
- Order placement form
- Positions table
- Order history
- Real-time price updates
### PortfolioPage
- Portfolio summary
- Holdings table
- Risk metrics
- Performance charts
### StrategiesPage
- Strategy list and management
- Strategy configuration
- Start/stop controls
- Performance metrics
### BacktestPage
- Backtest configuration
- Results display
- Export functionality
### SettingsPage
- Exchange management
- Configuration settings
- API key management
## Real-Time Updates
### WebSocket Connection
The frontend connects to the backend WebSocket endpoint (`/ws/`) for real-time updates:
- Price updates
- Order status changes
- Position updates
- Strategy status changes
### Implementation
```typescript
// Using WebSocket hook
import { useWebSocket } from '@/hooks/useWebSocket';
function TradingPage() {
const { data, connected } = useWebSocket('/ws/');
// Handle real-time price updates
useEffect(() => {
if (data?.type === 'price_update') {
// Update UI with new price
}
}, [data]);
}
```
## API Integration
### API Client
All API calls go through the API client which handles:
- Request/response serialization
- Error handling
- Authentication (if added)
```typescript
// Example API call
import { tradingApi } from '@/api/trading';
const placeOrder = async () => {
const order = await tradingApi.placeOrder({
exchangeId: 1,
symbol: 'BTC/USD',
side: 'buy',
type: 'market',
quantity: 0.1,
paperTrading: true
});
};
```
### React Query
The frontend uses React Query for:
- Data fetching
- Caching
- Automatic refetching
- Optimistic updates
```typescript
import { useQuery, useMutation } from '@tanstack/react-query';
import { portfolioApi } from '@/api/portfolio';
function PortfolioPage() {
const { data, isLoading } = useQuery({
queryKey: ['portfolio'],
queryFn: () => portfolioApi.getCurrent()
});
const updateMutation = useMutation({
mutationFn: portfolioApi.update,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['portfolio'] });
}
});
}
```
## State Management
### Component State
- Local component state with `useState`
- Form state management
- UI-only state (modals, tabs, etc.)
### Server State
- React Query for server state
- Automatic caching and synchronization
- Optimistic updates
### Global State
- Context API for theme, auth (if needed)
- Minimal global state - prefer server state
## Component Patterns
### Container/Presentational Pattern
```typescript
// Container component (handles data fetching)
function TradingPageContainer() {
const { data } = useQuery({ queryKey: ['orders'], queryFn: fetchOrders });
return <TradingPage orders={data} />;
}
// Presentational component (pure UI)
function TradingPage({ orders }) {
return (
<Box>
<OrdersTable orders={orders} />
</Box>
);
}
```
### Custom Hooks
Extract reusable logic into custom hooks:
```typescript
function useOrders() {
const { data, isLoading, error } = useQuery({
queryKey: ['orders'],
queryFn: () => tradingApi.getOrders()
});
const placeOrder = useMutation({
mutationFn: tradingApi.placeOrder
});
return { orders: data, isLoading, error, placeOrder };
}
```
## Error Handling
### API Errors
```typescript
try {
await tradingApi.placeOrder(order);
} catch (error) {
if (error.response?.status === 400) {
// Handle validation error
} else if (error.response?.status === 500) {
// Handle server error
}
}
```
### Error Boundaries
Use React Error Boundaries to catch and display errors gracefully:
```typescript
<ErrorBoundary fallback={<ErrorPage />}>
<TradingPage />
</ErrorBoundary>
```
## Performance Optimization
### Code Splitting
Use React.lazy for code splitting:
```typescript
const TradingPage = lazy(() => import('@/pages/TradingPage'));
<Suspense fallback={<Loading />}>
<TradingPage />
</Suspense>
```
### Memoization
Use React.memo, useMemo, and useCallback to prevent unnecessary re-renders:
```typescript
const MemoizedTable = React.memo(OrdersTable);
const filteredData = useMemo(() => {
return data.filter(/* ... */);
}, [data, filter]);
```
## Testing
See [Testing Guide](../developer/testing.md) for frontend testing strategies.
## Styling
- Material-UI components and theming
- Consistent design system
- Dark/light theme support
- Responsive design
## Accessibility
- Semantic HTML
- ARIA labels where needed
- Keyboard navigation
- Screen reader support

116
docs/deployment/README.md Normal file
View File

@@ -0,0 +1,116 @@
# Deployment Guide
This guide covers deploying Crypto Trader in various environments.
## Table of Contents
1. [AppImage Deployment](appimage.md) - Building and distributing AppImage
2. [Bluefin Linux](bluefin.md) - Bluefin Linux specific instructions
3. [PostgreSQL Setup](postgresql.md) - PostgreSQL configuration
4. [Updates](updates.md) - Update mechanism and versioning
## Deployment Options
### AppImage (Recommended)
- Single executable file
- No installation required
- Portable across Linux distributions
- Includes all dependencies
### From Source
- Full control over installation
- Customizable configuration
- Development and production use
## System Requirements
- **OS**: Linux (Bluefin recommended), macOS, Windows
- **Python**: 3.11+ (for source installation)
- **Node.js**: 18+ (for frontend)
- **Memory**: 4GB minimum, 8GB recommended
- **Storage**: 1GB+ for application and data
- **Network**: Internet connection required
- **Redis**: Version 5.0+ (for state management)
- **PostgreSQL**: Version 14+ (for database)
## Quick Deployment
### AppImage
1. Download AppImage
2. Make executable: `chmod +x crypto_trader-*.AppImage`
3. Run: `./crypto_trader-*.AppImage`
### From Source
1. Clone repository
2. Install dependencies: `pip install -r requirements.txt`
3. Install frontend dependencies: `cd frontend && npm install`
4. Start Redis (see Redis section below for options)
5. Start Celery worker: `celery -A src.worker.app worker --loglevel=info &`
6. Start backend: `uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000 &`
7. Start frontend: `cd frontend && npm run dev`
Or use the helper script:
```bash
./scripts/start_all.sh
```
## Required Services
### Redis
Redis is required for distributed state management and Celery background tasks (e.g., ML model training).
```bash
# Install (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, etc.) where `sudo` is not available, use the direct daemon mode option.
### Celery Worker
Celery handles background tasks like ML training:
```bash
# Start worker
celery -A src.worker.app worker --loglevel=info
# Start with specific queues
celery -A src.worker.app worker -Q ml_training,celery --loglevel=info
```
## Post-Deployment
After deployment:
1. Configure exchanges
2. Set up risk management
3. Verify Redis connection: `python scripts/verify_redis.py`
4. Test with paper trading
5. Review configuration
6. Start with small positions
## Production Considerations
- Use a process manager (systemd, supervisor) for services
- Configure Redis persistence (AOF or RDB)
- Set up monitoring and alerting
- Enable HTTPS for the API
- Configure proper firewall rules

View File

@@ -0,0 +1,171 @@
# PostgreSQL Setup
This guide covers optional PostgreSQL database configuration.
## When to Use PostgreSQL
PostgreSQL is recommended for:
- Large datasets (millions of trades)
- Multiple users
- Advanced queries
- Production deployments
- High-performance requirements
## Installation
### Install PostgreSQL
**Fedora/Bluefin**:
```bash
sudo dnf install postgresql postgresql-server
sudo postgresql-setup --initdb
sudo systemctl enable postgresql
sudo systemctl start postgresql
```
**Ubuntu/Debian**:
```bash
sudo apt-get install postgresql postgresql-contrib
sudo systemctl enable postgresql
sudo systemctl start postgresql
```
## Database Setup
### Create Database
```bash
sudo -u postgres psql
```
```sql
CREATE DATABASE crypto_trader;
CREATE USER crypto_trader_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE crypto_trader TO crypto_trader_user;
\q
```
### Configure Connection
Update `config.yaml`:
```yaml
database:
type: postgresql
host: localhost
port: 5432
database: crypto_trader
user: crypto_trader_user
password: ${DB_PASSWORD} # Use environment variable
```
### Set Environment Variable
```bash
export DB_PASSWORD='your_password'
```
Or add to `~/.bashrc`:
```bash
echo 'export DB_PASSWORD="your_password"' >> ~/.bashrc
```
## Migration from SQLite
### Export from SQLite
```bash
sqlite3 trading.db .dump > dump.sql
```
### Import to PostgreSQL
```bash
psql -U crypto_trader_user -d crypto_trader -f dump.sql
```
## Performance Tuning
### PostgreSQL Configuration
Edit `/etc/postgresql/*/main/postgresql.conf`:
```ini
shared_buffers = 256MB
effective_cache_size = 1GB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 4MB
min_wal_size = 1GB
max_wal_size = 4GB
```
### Indexes
Key indexes are created automatically. For custom queries, add indexes:
```sql
CREATE INDEX idx_trades_symbol_date ON trades(symbol, executed_at);
CREATE INDEX idx_market_data_symbol_timeframe ON market_data(symbol, timeframe, timestamp);
```
## Backup and Recovery
### Backup
```bash
pg_dump -U crypto_trader_user crypto_trader > backup.sql
```
### Restore
```bash
psql -U crypto_trader_user crypto_trader < backup.sql
```
### Automated Backups
Set up cron job:
```bash
0 2 * * * pg_dump -U crypto_trader_user crypto_trader > /backup/crypto_trader_$(date +\%Y\%m\%d).sql
```
## Security
### Connection Security
- Use strong passwords
- Restrict network access
- Use SSL connections for remote access
- Regular security updates
### User Permissions
- Use dedicated database user
- Grant only necessary permissions
- Don't use superuser for application
## Troubleshooting
**Connection refused?**
- Check PostgreSQL is running: `sudo systemctl status postgresql`
- Verify connection settings
- Check firewall rules
**Authentication failed?**
- Verify username and password
- Check `pg_hba.conf` configuration
- Review PostgreSQL logs
**Performance issues?**
- Check PostgreSQL configuration
- Review query performance
- Add appropriate indexes
- Monitor resource usage

131
docs/deployment/updates.md Normal file
View File

@@ -0,0 +1,131 @@
# Update Mechanism
This guide covers the built-in update mechanism for Crypto Trader.
## Update System
Crypto Trader includes a built-in update checker and installer for AppImage deployments.
## Update Check
### Automatic Check
Updates are checked on application startup (if enabled):
```yaml
updates:
check_on_startup: true
repository_url: "https://github.com/user/crypto_trader"
```
### Manual Check
Check for updates from the application:
1. Navigate to Help > Check for Updates
2. Application checks GitHub releases
3. Notifies if update available
## Update Process
### Automatic Update
1. **Check for Updates**
- Compares current version with latest release
- Downloads update information
2. **Download Update**
- Downloads new AppImage
- Shows progress
- Verifies download
3. **Install Update**
- Creates backup of current version
- Replaces with new version
- Makes executable
4. **Restart**
- Prompts to restart
- Launches new version
### Manual Update
1. Download new AppImage
2. Replace old file
3. Make executable: `chmod +x crypto_trader-*.AppImage`
4. Run new version
## Version Management
### Version Format
Follows semantic versioning: `MAJOR.MINOR.PATCH`
Example: `1.2.3`
### Version Comparison
- **Major**: Breaking changes
- **Minor**: New features (backward compatible)
- **Patch**: Bug fixes (backward compatible)
## Update Configuration
### Disable Auto-Check
```yaml
updates:
check_on_startup: false
```
### Custom Repository
```yaml
updates:
repository_url: "https://github.com/your-org/crypto_trader"
```
## Update Notifications
Users are notified when:
- Update is available on startup
- Manual check finds update
- Critical security update available
## Rollback
If update causes issues:
1. Locate backup: `crypto_trader-*.AppImage.backup`
2. Restore backup:
```bash
mv crypto_trader-*.AppImage.backup crypto_trader-*.AppImage
chmod +x crypto_trader-*.AppImage
```
3. Run previous version
## Best Practices
1. **Test Updates**: Test updates in development first
2. **Backup**: Always backup before updating
3. **Release Notes**: Review release notes before updating
4. **Staged Rollout**: Consider staged rollout for major updates
## Troubleshooting
**Update check fails?**
- Check internet connection
- Verify repository URL
- Review application logs
**Download fails?**
- Check disk space
- Verify download URL
- Check network connection
**Installation fails?**
- Check file permissions
- Verify AppImage integrity
- Review error logs

View File

@@ -0,0 +1,262 @@
# Web Architecture Deployment Guide
This guide covers deploying the new web-based architecture for Crypto Trader.
## Overview
The application has been migrated from PyQt6 desktop app to a modern web-based architecture:
- **Frontend**: React + TypeScript + Material-UI
- **Backend**: FastAPI (Python)
- **Deployment**: Docker
## Architecture
```
┌─────────────────────────────────────────┐
│ Web Browser (Client) │
│ React + Material-UI + TypeScript │
└─────────────────┬───────────────────────┘
│ HTTP/WebSocket
┌─────────────────▼───────────────────────┐
│ Python Backend API (FastAPI) │
│ - Trading Engine (existing code) │
│ - Strategy Framework (existing code) │
│ - Portfolio Management (existing code) │
└─────────────────┬───────────────────────┘
┌─────────────────▼───────────────────────┐
│ Database (SQLite/PostgreSQL) │
└─────────────────────────────────────────┘
```
## Quick Start
### Development
1. **Start Backend**:
```bash
cd backend
python -m uvicorn main:app --reload --port 8000
```
2. **Start Frontend**:
```bash
cd frontend
npm install
npm run dev
```
3. **Access Application**:
- Frontend: http://localhost:3000
- API Docs: http://localhost:8000/docs
- API: http://localhost:8000/api
### Docker Deployment
1. **Build and Run**:
```bash
docker-compose up --build
```
2. **Access Application**:
- Application: http://localhost:8000
- API Docs: http://localhost:8000/docs
## API Endpoints
### Trading
- `POST /api/trading/orders` - Create order
- `GET /api/trading/orders` - List orders
- `GET /api/trading/orders/{id}` - Get order
- `POST /api/trading/orders/{id}/cancel` - Cancel order
- `GET /api/trading/positions` - Get positions
- `GET /api/trading/balance` - Get balance
### Portfolio
- `GET /api/portfolio/current` - Get current portfolio
- `GET /api/portfolio/history` - Get portfolio history
- `POST /api/portfolio/positions/update-prices` - Update prices
### Strategies
- `GET /api/strategies/` - List strategies
- `GET /api/strategies/available` - List available strategy types
- `POST /api/strategies/` - Create strategy
- `GET /api/strategies/{id}` - Get strategy
- `PUT /api/strategies/{id}` - Update strategy
- `DELETE /api/strategies/{id}` - Delete strategy
- `POST /api/strategies/{id}/start` - Start strategy
- `POST /api/strategies/{id}/stop` - Stop strategy
### Backtesting
- `POST /api/backtesting/run` - Run backtest
- `GET /api/backtesting/results/{id}` - Get backtest results
### Exchanges
- `GET /api/exchanges/` - List exchanges
- `GET /api/exchanges/{id}` - Get exchange
### WebSocket
- `WS /ws/` - WebSocket connection for real-time updates
## Configuration
### Environment Variables
Create `.env` file:
```env
# API Configuration
VITE_API_URL=http://localhost:8000
VITE_WS_URL=ws://localhost:8000/ws/
# Database (optional)
DATABASE_URL=sqlite:///./data/crypto_trader.db
```
### Docker Environment
Environment variables can be set in `docker-compose.yml`:
```yaml
environment:
- DATABASE_URL=sqlite:///./data/crypto_trader.db
- PYTHONPATH=/app
```
## Data Persistence
Data is stored in the `./data` directory, which is mounted as a volume in Docker:
```yaml
volumes:
- ./data:/app/data
```
## Development
### Backend Development
1. Install dependencies:
```bash
pip install -r requirements.txt
pip install -r backend/requirements.txt
```
2. Run with hot-reload:
```bash
python -m uvicorn backend.main:app --reload
```
### Frontend Development
1. Install dependencies:
```bash
cd frontend
npm install
```
2. Run development server:
```bash
npm run dev
```
3. Build for production:
```bash
npm run build
```
## Production Deployment
### Docker Production
1. Build production image:
```bash
docker build -t crypto-trader:latest .
```
2. Run container:
```bash
docker run -d \
-p 8000:8000 \
-v $(pwd)/data:/app/data \
-v $(pwd)/config:/app/config \
--name crypto-trader \
crypto-trader:latest
```
### Reverse Proxy (Nginx)
Example Nginx configuration:
```nginx
server {
listen 80;
server_name crypto-trader.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
```
## Migration from PyQt6
The existing Python code has been preserved:
- Trading engine (`src/trading/`)
- Strategy framework (`src/strategies/`)
- Portfolio tracker (`src/portfolio/`)
- Backtesting engine (`src/backtesting/`)
- All other core modules
Only the UI layer has been replaced with a web interface.
## Troubleshooting
### Backend Issues
- **Port already in use**: Change port in `docker-compose.yml` or use `--port` flag
- **Database errors**: Check database file permissions in `./data` directory
- **Import errors**: Ensure `PYTHONPATH=/app` is set
### Frontend Issues
- **API connection errors**: Check `VITE_API_URL` in `.env` file
- **WebSocket connection fails**: Verify WebSocket URL and backend is running
- **Build errors**: Clear `node_modules` and reinstall: `rm -rf node_modules && npm install`
### Docker Issues
- **Build fails**: Check Dockerfile syntax and dependencies
- **Container won't start**: Check logs: `docker-compose logs`
- **Volume permissions**: Ensure `./data` directory is writable
## Benefits of Web Architecture
1. **Modern UI**: Access to entire web ecosystem (Material-UI, charts, etc.)
2. **Cross-platform**: Works on any device with a browser
3. **Easier deployment**: Docker is simpler than AppImage
4. **Better development**: Hot-reload, better tooling
5. **Maintainability**: Easier to update and deploy
6. **Accessibility**: Access from anywhere via browser
## Next Steps
1. Add authentication (JWT tokens)
2. Implement WebSocket price updates
3. Add more charting capabilities
4. Enhance strategy management UI
5. Add mobile-responsive design

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

View File

@@ -0,0 +1,156 @@
# Frontend UI Enhancement Summary
This document summarizes the comprehensive frontend enhancements completed to ensure all documented features are accessible in the UI and improve UX transparency.
## New Pages Added
### 1. Strategy Management Page (`/strategies`)
- Full CRUD operations for strategies
- Create, edit, delete strategies
- Start/stop strategy controls
- Parameter configuration UI for all strategy types (RSI, MACD, Moving Average, Confirmed, Divergence, Bollinger, Consensus, DCA, Grid, Momentum)
- Strategy status indicators
- Performance metrics display
### 2. Manual Trading Page (`/trading`)
- Order placement form with all order types (Market, Limit, Stop Loss, Take Profit, Trailing Stop, OCO, Iceberg)
- Active orders table with cancel functionality
- Order history with filters
- Position management with close position functionality
- Real-time order and position updates
## Enhanced Existing Pages
### Dashboard
- System health indicators (WebSocket, Database, Exchange connections)
- Operations panel showing running operations
- Data freshness indicators
- Enhanced error handling with retry options
- Loading states for all async operations
- Real-time status updates
### Portfolio
- Position closing interface
- Portfolio allocation pie charts
- Data freshness indicators
- Enhanced export functionality with success/error feedback
- Card-based position view with detailed P&L
### Backtesting
- Progress overlay for long-running backtests
- Operations panel integration
- Enhanced error messages
- Info card about parameter optimization (requires backend API)
### Settings
- Alert history view showing triggered alerts
- Enhanced exchange management with status indicators
- Better connection testing with detailed feedback
- Improved error handling throughout
## New Components Created
### UX Components
- `LoadingSkeleton` - Loading placeholders for tables, cards, lists
- `ProgressOverlay` - Overlay with progress indicator for long operations
- `ErrorDisplay` - Enhanced error display with retry functionality
- `StatusIndicator` - Connection status indicators
- `SystemHealth` - System health dashboard widget
- `DataFreshness` - Timestamp indicators showing data age
- `HelpTooltip` - Contextual help tooltips
- `InfoCard` - Collapsible information cards
- `OperationsPanel` - Panel showing running operations and progress
### Feature Components
- `StrategyDialog` - Create/edit strategy dialog with tabs
- `StrategyParameterForm` - Dynamic parameter forms for each strategy type
- `OrderForm` - Order placement form with all order types
- `PositionCard` - Position display card with close functionality
- `AlertHistory` - Alert history table component
## Infrastructure Improvements
### Error Handling
- Replaced all `alert()` calls with Snackbar notifications
- Created `SnackbarContext` for global error/success messaging
- Added error boundaries with recovery options
- Inline validation errors in forms
### Real-time Updates
- Enhanced WebSocket hook with message type handling
- Created `useRealtimeData` hook for automatic query invalidation
- Message subscription system for different event types
- Real-time updates for orders, positions, prices, alerts, strategy signals
### State Management
- Better loading states throughout
- Progress indicators for long operations
- Data freshness tracking
- Operation visibility
## Navigation Updates
- Added "Strategies" menu item
- Added "Trading" menu item
- Improved navigation organization
## Testing Notes
The following areas should be tested:
1. **Strategy Management**
- Create/edit/delete strategies
- Start/stop strategies
- Parameter validation
- Strategy type switching
2. **Trading**
- Order placement (all order types)
- Order cancellation
- Position closing
- Real-time updates
3. **Real-time Features**
- WebSocket connection/reconnection
- Message handling
- Query invalidation on updates
4. **Error Handling**
- Network errors
- Validation errors
- Backend errors
- Retry functionality
5. **Loading States**
- Skeleton loaders
- Progress overlays
- Button disabled states
## Documentation Updates Needed
1. **User Manual Updates**
- Add Strategy Management section
- Add Manual Trading section
- Update Dashboard documentation
- Update Portfolio documentation with new features
- Update Settings documentation with alert history
2. **API Documentation**
- Document WebSocket message types
- Document new endpoints if any
- Update response schemas
3. **Developer Documentation**
- Component architecture
- State management patterns
- WebSocket integration guide
- Error handling patterns
## Known Limitations
1. **Parameter Optimization**: UI structure is in place, but requires backend API endpoints for optimization methods (Grid Search, Genetic Algorithm, Bayesian Optimization)
2. **Advanced Order Types**: OCO and Iceberg orders have UI support but may need backend implementation verification
3. **Exchange Status**: Exchange connection status is displayed but may need backend health check endpoints for accurate status

View File

@@ -0,0 +1,192 @@
# Pairs Trading Strategy - Configuration Guide
This guide walks you through configuring and enabling the Statistical Arbitrage (Pairs Trading) strategy in your crypto trading application.
## Overview
Pairs Trading is a market-neutral strategy that profits from the relative price movements between two correlated assets. When the spread between two assets diverges beyond a statistical threshold (Z-Score), the strategy generates signals to trade the reversion.
---
## Step 1: Navigate to Strategy Management
1. Open your application in the browser (typically `http://localhost:5173`)
2. Click on **"Strategies"** in the navigation menu
3. You'll see the Strategy Management page
---
## Step 2: Create a New Strategy
1. Click the **"Create Strategy"** button in the top-right corner
2. The Strategy Dialog will open
---
## Step 3: Configure Basic Settings
Fill in the following fields:
| Field | Description | Example |
|-------|-------------|---------|
| **Strategy Name** | A descriptive name for your strategy | `SOL-AVAX Pairs Trade` |
| **Strategy Type** | Select from dropdown | `Statistical Arbitrage (Pairs)` |
| **Primary Symbol** | The main asset to trade | `SOL/USD` |
| **Exchange** | Your configured exchange | `Coinbase` or `Binance` |
| **Timeframe** | Candlestick interval | `1h` (recommended) |
---
## Step 4: Configure Pairs Trading Parameters
After selecting "Statistical Arbitrage (Pairs)" as the strategy type, you'll see the parameters section:
### Required Parameters:
| Parameter | Description | Default | Recommended Range |
|-----------|-------------|---------|-------------------|
| **Second Symbol** | The correlated asset to pair with | - | `AVAX/USD`, `ETH/USD`, etc. |
| **Lookback Period** | Rolling window for statistics | `20` | 15-50 |
| **Z-Score Threshold** | Trigger level for signals | `2.0` | 1.5-3.0 |
### Parameter Explanations:
- **Second Symbol**: Choose an asset that moves similarly to your primary symbol. Common pairs include:
- `BTC/USD``ETH/USD` (highly correlated)
- `SOL/USD``AVAX/USD` (Layer 1s)
- `DOGE/USD``SHIB/USD` (meme coins)
- **Lookback Period**: Number of candles used to calculate rolling mean and standard deviation. Higher values = smoother but slower to react.
- **Z-Score Threshold**: How many standard deviations from the mean before triggering:
- `1.5` = More frequent signals, smaller moves
- `2.0` = Balanced (default)
- `2.5-3.0` = Fewer signals, larger moves
---
## Step 5: Additional Settings
| Setting | Description | Recommendation |
|---------|-------------|----------------|
| **Paper Trading** | Enable for testing | ✅ Start with Paper Trading ON |
| **Auto Execute** | Automatically place trades | ❌ Keep OFF initially to observe signals |
---
## Step 6: Save and Enable
1. Click **"Create"** to save the strategy
2. The strategy will appear in your strategy list with status "Disabled"
3. Click the **▶️ Play** button to enable the strategy
---
## Step 7: Monitor the Spread
Once enabled, scroll down on the Strategies page to see the **Pairs Trading Analysis** section:
- **Current Spread**: The ratio of Primary Symbol / Secondary Symbol prices
- **Z-Score**: How many standard deviations the current spread is from its mean
- **Signal State**: Shows if a signal is active (Long Spread, Short Spread, or Neutral)
### Understanding the Charts:
1. **Spread History Chart**: Shows the ratio over time
2. **Z-Score Chart**: Shows statistical deviation with threshold lines
- Green dashed line: Buy threshold (-2.0)
- Red dashed line: Sell threshold (+2.0)
---
## Signal Logic
| Condition | Signal | Action |
|-----------|--------|--------|
| Z-Score > +Threshold | **SELL** | Sell Primary, Buy Secondary |
| Z-Score < -Threshold | **BUY** | Buy Primary, Sell Secondary |
| Z-Score between thresholds | **HOLD** | No action (neutral) |
### Example:
If you're trading `SOL/USD` vs `AVAX/USD`:
- **Z-Score = +2.5**: SOL is overvalued relative to AVAX → Sell SOL, Buy AVAX
- **Z-Score = -2.5**: SOL is undervalued relative to AVAX → Buy SOL, Sell AVAX
---
## Tips for Success
1. **Choose Correlated Pairs**: The strategy works best with assets that historically move together. Check correlation before pairing.
2. **Start with Paper Trading**: Always test with paper trading first to understand signal frequency and behavior.
3. **Consider Timeframe**:
- `1h` is good for daily monitoring
- `4h` for longer-term positions
- `15m` for more active trading (higher risk)
4. **Monitor Volatility**: The strategy performs best in ranging/mean-reverting markets. Trending markets can cause losses.
5. **Adjust Threshold**: If you get too many signals, increase the threshold. Too few? Lower it.
---
## Troubleshooting
| Issue | Solution |
|-------|----------|
| No data appearing | Ensure both symbols are available on your selected exchange |
| Z-Score always near 0 | Try increasing lookback period or verify price data is flowing |
| Too many signals | Increase Z-Score threshold (e.g., 2.5 or 3.0) |
| Strategy not executing | Check if Auto Execute is enabled in Settings |
---
## Example Configuration
```
Name: ETH-SOL Mean Reversion
Type: Statistical Arbitrage (Pairs)
Primary Symbol: ETH/USD
Second Symbol: SOL/USD
Lookback Period: 20
Z-Score Threshold: 2.0
Timeframe: 1h
Paper Trading: ON
```
This configuration would:
1. Monitor the ETH/SOL price ratio
2. Generate a BUY signal when ETH is historically cheap vs SOL (Z-Score < -2)
3. Generate a SELL signal when ETH is historically expensive vs SOL (Z-Score > +2)
---
## How Execution Works
When you click **Start** on a Pairs Trading strategy:
1. **Strategy Scheduler starts** - Runs the strategy on a 60-second interval (configurable)
2. **Each tick**:
- Fetches current prices for both symbols
- Calculates spread ratio and Z-Score
- If Z-Score exceeds threshold → generates signal
3. **Signal execution** (if enabled):
- Executes both legs simultaneously
- Primary symbol: BUY or SELL based on signal
- Secondary symbol: Opposite action
4. **Status updates** visible on Strategies page
---
## Real-Time Status Panel
When strategies are running, you'll see a **green status panel** on the Strategies page showing:
- Strategy name and symbol
- Start time
- Number of signals generated
- Last signal type and price
- Last execution tick time
This updates every 5 seconds automatically.

222
docs/migration_guide.md Normal file
View File

@@ -0,0 +1,222 @@
# Migration Guide: PyQt6 to Web Architecture
This guide explains the migration from PyQt6 desktop application to web-based architecture.
## Overview
The application has been migrated from a PyQt6 desktop app to a modern web-based architecture while preserving 90% of the existing Python backend code.
## What Changed
### Architecture
**Before (PyQt6)**:
```
PyQt6 UI → Direct Python calls → Services → Database
```
**After (Web)**:
```
React UI → HTTP/WebSocket → FastAPI → Services → Database
```
### Code Structure
**Before**:
```
crypto_trader/
├── src/
│ ├── ui/ # PyQt6 widgets
│ ├── trading/ # Trading engine
│ ├── strategies/ # Strategy framework
│ └── ...
└── packaging/ # AppImage build
```
**After**:
```
crypto_trader/
├── backend/ # FastAPI application
│ ├── api/ # API endpoints
│ └── core/ # Dependencies, schemas
├── frontend/ # React application
│ └── src/
│ ├── pages/ # Page components
│ ├── api/ # API client
│ └── ...
├── src/ # Existing Python code (unchanged)
└── docker-compose.yml
```
## What Was Preserved
### 100% Preserved (No Changes)
- `src/trading/engine.py` - Trading engine
- `src/strategies/` - Strategy framework
- `src/portfolio/` - Portfolio tracker
- `src/backtesting/` - Backtesting engine
- `src/risk/` - Risk management
- `src/data/` - Data collection
- `src/exchanges/` - Exchange integrations
- `src/core/database.py` - Database models
- All business logic
### What Was Replaced
- **UI Layer**: PyQt6 widgets → React components
- **Communication**: Direct function calls → HTTP API
- **Real-time Updates**: Signal/slot → WebSocket
- **Deployment**: AppImage → Docker
## Migration Steps
### 1. API Layer Creation
Created FastAPI application that wraps existing services:
```python
# backend/api/trading.py
@router.post("/orders")
async def create_order(order_data: OrderCreate):
# Uses existing trading_engine.execute_order()
order = trading_engine.execute_order(...)
return OrderResponse.from_orm(order)
```
### 2. Frontend Development
Created React frontend with:
- Material-UI for modern components
- React Query for data fetching
- TypeScript for type safety
- WebSocket for real-time updates
### 3. Docker Deployment
Created Docker setup for easy deployment:
- Multi-stage build (frontend + backend)
- Single container deployment
- Volume mounts for data persistence
## Running the New Architecture
### Development
1. **Backend**:
```bash
cd backend
python -m uvicorn main:app --reload
```
2. **Frontend**:
```bash
cd frontend
npm install
npm run dev
```
### Production
```bash
docker-compose up --build
```
Access at: http://localhost:8000
## API Endpoints
All functionality is now available via REST API:
- **Trading**: `/api/trading/*`
- **Portfolio**: `/api/portfolio/*`
- **Strategies**: `/api/strategies/*`
- **Backtesting**: `/api/backtesting/*`
- **WebSocket**: `/ws/` for real-time updates
See API documentation at: http://localhost:8000/docs
## Key Differences
### UI Development
**Before (PyQt6)**:
```python
# Complex QSS styling
self.setStyleSheet("""
QPushButton {
background-color: #1E1E1E;
...
}
""")
```
**After (React)**:
```tsx
// Modern CSS-in-JS
<Button variant="contained" color="primary">
Place Order
</Button>
```
### Data Fetching
**Before (PyQt6)**:
```python
# Direct function calls
order = trading_engine.execute_order(...)
```
**After (React)**:
```tsx
// API calls with React Query
const { data } = useQuery({
queryKey: ['orders'],
queryFn: () => tradingApi.getOrders()
})
```
### Real-time Updates
**Before (PyQt6)**:
```python
# Signal/slot connections
collector.signals.price_updated.connect(self._on_price_update)
```
**After (React)**:
```tsx
// WebSocket hook
const { lastMessage } = useWebSocket('ws://localhost:8000/ws/')
```
## Benefits
1. **Modern UI**: Access to entire web ecosystem
2. **Cross-platform**: Works on any device
3. **Easier deployment**: Docker vs AppImage
4. **Better development**: Hot-reload, better tooling
5. **Maintainability**: Easier to update
6. **Accessibility**: Access from anywhere
## Backward Compatibility
The existing Python code remains unchanged. You can still:
- Import and use services directly
- Run existing tests
- Use the code in other projects
## Next Steps
1. Add authentication (JWT)
2. Enhance WebSocket integration
3. Add more charting features
4. Improve mobile responsiveness
5. Add more strategy management features
## Support
For issues or questions:
- Check API docs: http://localhost:8000/docs
- Review deployment guide: `docs/deployment/web_architecture.md`
- Check backend logs: `docker-compose logs`

5
docs/requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
sphinx>=7.0.0
sphinx-rtd-theme>=1.3.0
sphinx-autodoc-typehints>=1.24.0
sphinxcontrib-napoleon>=0.7

View File

@@ -0,0 +1,182 @@
# Algorithm Improvements and New Features
This document describes the recent algorithm improvements implemented to improve trading success rates.
## Overview
Several advanced algorithms and strategies have been added to improve trade success rates and reduce false signals. These improvements leverage multi-indicator confirmation, divergence detection, ensemble methods, and advanced risk management.
## New Strategies
### 1. Confirmed Strategy (Multi-Indicator Confirmation)
**Purpose**: Reduce false signals by requiring multiple indicators to agree before generating a trade signal.
**How It Works**:
- Combines signals from RSI, MACD, and Moving Average indicators
- Only generates signals when a configurable number of indicators agree (default: 2)
- Calculates signal strength based on the level of agreement
**Benefits**:
- 20-30% reduction in false signals
- Higher confidence trades
- Better win rate through confirmation
**When to Use**:
- When you want to reduce false signals
- For more conservative trading approach
- In markets where single indicators are unreliable
### 2. Divergence Strategy
**Purpose**: Identify potential trend reversals by detecting divergences between price and indicators.
**How It Works**:
- Detects bullish divergence: Price makes lower low, indicator makes higher low → BUY signal
- Detects bearish divergence: Price makes higher high, indicator makes lower high → SELL signal
- Works with RSI or MACD indicators
**Benefits**:
- 15-25% improvement in entry timing
- Excellent for ranging markets
- Identifies reversal points before they happen
**When to Use**:
- In ranging/consolidating markets
- For identifying trend reversals
- When looking for contrarian signals
### 3. Bollinger Bands Mean Reversion
**Purpose**: Trade mean reversion in ranging markets using Bollinger Bands.
**How It Works**:
- Buys when price touches lower band in uptrend
- Sells at middle band for profit-taking
- Includes trend filter to avoid counter-trend trades
**Benefits**:
- Works well in ranging markets
- Clear entry and exit signals
- Risk-controlled through trend filter
**When to Use**:
- In ranging/consolidating markets
- For mean reversion trading
- When volatility is moderate
### 4. Consensus Strategy (Ensemble)
**Purpose**: Combine signals from multiple strategies using weighted voting to improve overall performance.
**How It Works**:
- Aggregates signals from multiple registered strategies
- Uses performance-based weighting (better performing strategies have more weight)
- Only executes when minimum number of strategies agree
**Benefits**:
- 15-20% overall improvement through consensus
- Dynamic weighting based on recent performance
- Reduces reliance on single strategy
**When to Use**:
- When you want to combine multiple strategies
- For more robust signal generation
- When trading with multiple indicators/approaches
## Enhanced Risk Management
### ATR-Based Dynamic Stop Loss
**Purpose**: Improve stop loss placement by adapting to market volatility.
**How It Works**:
- Calculates stop distance based on Average True Range (ATR)
- Stops automatically adjust to market volatility
- Tighter stops in low volatility, wider in high volatility
- Works with both fixed and trailing stops
**Benefits**:
- 10-15% better risk-adjusted returns
- Fewer stop-outs during normal market noise
- Better adaptation to market conditions
**Usage**:
```python
# Set ATR-based stop loss
risk_manager.update_stop_loss(
position_id=1,
stop_price=entry_price,
use_atr=True,
atr_multiplier=Decimal('2.0'), # Stop distance = 2 × ATR
atr_period=14,
ohlcv_data=market_data,
trailing=True # Enable trailing stop
)
```
## Advanced Features
### Trend Filtering
All strategies can now use optional ADX-based trend filtering:
- Filters out signals when ADX < threshold (weak trend/chop)
- Only allows BUY signals in uptrends, SELL in downtrends
- Reduces trades in choppy/ranging markets
**Enable in Strategy Parameters**:
- Set `use_trend_filter: true` in strategy parameters
- Configure `min_adx` threshold (default: 25.0)
## Expected Improvements
When all improvements are implemented and properly configured:
- **Overall Win Rate**: 30-40% improvement
- **False Signals**: 20-30% reduction
- **Risk-Adjusted Returns**: 10-15% improvement
- **Entry Timing**: 15-25% improvement
## Best Practices
1. **Start with Paper Trading**: Always test new strategies in paper trading mode first
2. **Combine Strategies**: Use Consensus Strategy to combine multiple approaches
3. **Use ATR Stops**: Enable ATR-based stops for better risk management
4. **Enable Trend Filters**: Use trend filtering in choppy markets
5. **Backtest Thoroughly**: Backtest all strategies before live trading
6. **Monitor Performance**: Regularly review strategy performance and adjust parameters
7. **Gradual Implementation**: Add new strategies gradually and monitor their impact
## Migration Guide
### Updating Existing Strategies
Existing strategies can benefit from new features:
1. **Add Trend Filtering**:
- Add `use_trend_filter: true` to strategy parameters
- Signals will be automatically filtered
2. **Upgrade to ATR Stops**:
- Update stop loss settings to use `use_atr: true`
- Provide OHLCV data for ATR calculation
3. **Combine with Consensus**:
- Create a Consensus Strategy
- Include your existing strategies
- Benefit from ensemble methods
## Technical Details
For technical implementation details, see:
- [Strategy Framework Architecture](../architecture/strategy_framework.md)
- [Risk Management Architecture](../architecture/risk_management.md)
- [Creating Custom Strategies](../developer/creating_strategies.md)

View File

@@ -0,0 +1,23 @@
# Crypto Trader User Manual
Welcome to the Crypto Trader user manual. This guide will help you get started with the application and use all its features effectively.
## Table of Contents
1. [Getting Started](getting_started.md) - Installation and first steps
2. [Configuration](configuration.md) - Setting up the application
3. [Trading](trading.md) - How to trade cryptocurrencies
4. [Strategies](strategies.md) - Creating and managing trading strategies
5. [Backtesting](backtesting.md) - Testing strategies on historical data
6. [Portfolio](portfolio.md) - Managing your portfolio
7. [Alerts](alerts.md) - Setting up price and indicator alerts
8. [Reporting](reporting.md) - Exporting data and generating reports
9. [Troubleshooting](troubleshooting.md) - Common issues and solutions
10. [FAQ](faq.md) - Frequently asked questions
## Quick Links
- [Installation Guide](getting_started.md#installation)
- [First Trade](trading.md#placing-your-first-trade)
- [Creating a Strategy](strategies.md#creating-a-strategy)
- [Running a Backtest](backtesting.md#running-a-backtest)

141
docs/user_manual/alerts.md Normal file
View File

@@ -0,0 +1,141 @@
# Alerts Guide
Set up alerts for price movements, indicators, and system events.
## Alert Types
### Price Alerts
Get notified when prices reach specific levels:
- **Price Above**: Alert when price exceeds threshold
- **Price Below**: Alert when price drops below threshold
- **Price Change**: Alert on percentage change
### Indicator Alerts
Alert on technical indicator conditions:
- **RSI**: Overbought/oversold conditions
- **MACD**: Crossover signals
- **Moving Average**: Price crosses moving average
- **Bollinger Bands**: Price touches bands
### Risk Alerts
Get notified about risk conditions:
- **Drawdown**: Portfolio drawdown exceeds limit
- **Daily Loss**: Daily loss exceeds threshold
- **Position Size**: Position exceeds limit
- **Margin**: Margin level warnings
### System Alerts
System and application alerts:
- **Connection Lost**: Exchange connection issues
- **Order Filled**: Trade execution notifications
- **Error**: Application errors
- **Update Available**: New version available
## Creating Alerts
1. Navigate to Alerts view
2. Click "Create Alert"
3. Select alert type
4. Configure conditions:
- **Symbol**: Trading pair
- **Condition**: Alert trigger condition
- **Value**: Threshold value
5. Choose notification method:
- **Desktop**: System notification
- **Sound**: Audio alert
- **Email**: Email notification (if configured)
6. Set alert name
7. Click "Save"
## Alert Examples
### Price Alert Example
**Alert**: BTC Price Above $50,000
- **Type**: Price Alert
- **Symbol**: BTC/USD
- **Condition**: Price Above
- **Value**: 50000
- **Notification**: Desktop + Sound
### RSI Alert Example
**Alert**: BTC RSI Oversold
- **Type**: Indicator Alert
- **Symbol**: BTC/USD
- **Indicator**: RSI
- **Condition**: RSI Below
- **Value**: 30
- **Timeframe**: 1h
### Risk Alert Example
**Alert**: Portfolio Drawdown Warning
- **Type**: Risk Alert
- **Condition**: Drawdown Exceeds
- **Value**: 10%
- **Notification**: Desktop + Email
## Managing Alerts
### Enabling/Disabling
- Toggle alerts on/off without deleting
- Disabled alerts don't trigger
- Useful for temporary disabling
### Editing Alerts
1. Select the alert
2. Click "Edit"
3. Modify conditions
4. Save changes
### Deleting Alerts
1. Select the alert
2. Click "Delete"
3. Confirm deletion
## Alert History
View all triggered alerts in the Alert History tab:
1. Navigate to **Settings** page
2. Click on **Alert History** tab
3. View the table showing:
- **Alert Name**: Name of the alert
- **Type**: Alert type (price, indicator, risk, system)
- **Condition**: The condition that triggered (e.g., "BTC/USD @ $50000")
- **Triggered At**: Timestamp when the alert fired
- **Status**: Whether the alert is currently enabled or disabled
The alert history automatically refreshes every 5 seconds to show newly triggered alerts. Only alerts that have been triggered at least once appear in this view.
## Notification Settings
Configure notification preferences:
1. Navigate to Settings > Notifications
2. Configure:
- **Desktop Notifications**: Enable/disable
- **Sound Alerts**: Enable/disable
- **Email Notifications**: Configure email settings
3. Test notifications
## Best Practices
1. **Set Meaningful Thresholds**: Avoid too many alerts
2. **Use Multiple Channels**: Desktop + sound for important alerts
3. **Review Regularly**: Clean up unused alerts
4. **Test Alerts**: Verify alerts work correctly
5. **Monitor Alert History**: Track what triggered

View File

@@ -0,0 +1,217 @@
# Backtesting Guide
This guide explains how to use the backtesting feature to evaluate trading strategies on historical data.
## Running a Backtest
1. Navigate to the **Backtesting** page
2. Configure your backtest in the form:
- **Strategy**: Select a strategy from the dropdown (required)
- **Symbol**: Trading pair to test (e.g., BTC/USD)
- **Exchange**: Data source exchange (e.g., coinbase)
- **Timeframe**: Data timeframe (1m, 5m, 15m, 1h, 4h, 1d) - 1h recommended for most strategies
- **Start Date**: Beginning of test period (required)
- **End Date**: End of test period (required)
- **Initial Capital**: Starting capital in USD (default: $100)
- **Slippage (%)**: Expected slippage percentage (default: 0.1%)
- **Fee Rate (%)**: Trading fee percentage (default: 0.1%)
3. Click **Run Backtest**
4. A progress overlay will appear showing the backtest is running
5. An operations panel will show the running backtest with status
6. Wait for completion (you'll receive a success notification)
7. Review results in the **Backtest Results** section below
## Understanding Results
The backtest results include:
- **Total Return**: Overall percentage return
- **Sharpe Ratio**: Risk-adjusted return metric (higher is better)
- **Sortino Ratio**: Downside risk-adjusted return (higher is better)
- **Max Drawdown**: Largest peak-to-trough decline
- **Win Rate**: Percentage of profitable trades
- **Total Trades**: Number of trades executed
- **Final Value**: Portfolio value at end of backtest
## Exporting Results
After a backtest completes, you can export the results:
1. In the backtest results section, find the export buttons
2. **Export CSV**:
- Click **Export CSV** button
- Downloads a CSV file with all trades from the backtest
- File includes: timestamp, side, price, quantity, value
3. **Export PDF**:
- Click **Export PDF** button
- Generates a comprehensive PDF report
- Includes charts, metrics, and trade analysis
Both exports are automatically named with the current date for easy organization.
## Parameter Optimization
Parameter optimization allows you to automatically find the best strategy parameters. This feature requires backend API support and will be available once the optimization endpoints are implemented.
The UI includes an information card explaining this feature. When available, you'll be able to:
- Select parameters to optimize
- Set parameter ranges
- Choose optimization method (Grid Search, Genetic Algorithm, Bayesian Optimization)
- View optimization progress
- Compare optimization results
## Interpreting Metrics
- **Sharpe Ratio > 1**: Good risk-adjusted returns
- **Max Drawdown < 20%**: Acceptable risk level
- **Win Rate > 50%**: More winning than losing trades
# Backtesting Guide
Learn how to test your trading strategies on historical data.
## What is Backtesting?
Backtesting is the process of testing a trading strategy on historical data to evaluate its performance before risking real money.
## Running a Backtest
1. Navigate to Backtest view
2. Select a strategy
3. Configure backtest parameters:
- **Start Date**: Beginning of test period
- **End Date**: End of test period
- **Initial Capital**: Starting capital
- **Symbol**: Trading pair to test
- **Timeframe**: Data timeframe
4. Click "Run Backtest"
5. Wait for completion
6. Review results
## Backtest Parameters
### Time Period
- **Start Date**: When to begin the backtest
- **End Date**: When to end the backtest
- **Duration**: Length of test period
- Longer periods provide more reliable results
### Capital Settings
- **Initial Capital**: Starting amount (e.g., $10,000)
- **Currency**: Base currency (USD, EUR, etc.)
### Market Settings
- **Symbol**: Trading pair (BTC/USD, ETH/USD, etc.)
- **Timeframe**: Data granularity (1m, 5m, 1h, 1d)
- **Exchange**: Historical data source
## Understanding Results
### Performance Metrics
- **Total Return**: Overall profit/loss percentage
- **Final Capital**: Ending portfolio value
- **Sharpe Ratio**: Risk-adjusted return measure
- **Sortino Ratio**: Downside risk-adjusted return
- **Max Drawdown**: Largest peak-to-trough decline
- **Win Rate**: Percentage of profitable trades
### Trade Analysis
- **Total Trades**: Number of trades executed
- **Winning Trades**: Number of profitable trades
- **Losing Trades**: Number of unprofitable trades
- **Average Win**: Average profit per winning trade
- **Average Loss**: Average loss per losing trade
- **Profit Factor**: Ratio of gross profit to gross loss
### Charts
- **Equity Curve**: Portfolio value over time
- **Drawdown Chart**: Drawdown periods
- **Trade Distribution**: Win/loss distribution
- **Monthly Returns**: Performance by month
## Realistic Backtesting
Crypto Trader includes realistic backtesting features:
### Slippage
Slippage simulates the difference between expected and actual execution prices.
- **Default**: 0.1% for market orders
- **Configurable**: Adjust based on market conditions
- **Market Impact**: Larger orders have more slippage
### Fees
Trading fees are automatically included:
- **Maker Fees**: For limit orders (typically 0.1%)
- **Taker Fees**: For market orders (typically 0.2%)
- **Exchange-Specific**: Fees vary by exchange
### Order Execution
- **Market Orders**: Execute at current price + slippage
- **Limit Orders**: Execute only if price reaches limit
- **Partial Fills**: Large orders may fill partially
## Parameter Optimization
Optimize strategy parameters for better performance:
1. Select strategy
2. Choose parameters to optimize
3. Set parameter ranges
4. Select optimization method:
- **Grid Search**: Test all combinations
- **Genetic Algorithm**: Evolutionary optimization
- **Bayesian Optimization**: Efficient parameter search
5. Run optimization
6. Review results and select best parameters
## Best Practices
1. **Use Sufficient Data**: Test on at least 6-12 months of data
2. **Avoid Overfitting**: Don't optimize too aggressively
3. **Test Multiple Periods**: Verify performance across different market conditions
4. **Consider Fees**: Always include realistic fees
5. **Check Slippage**: Account for execution costs
6. **Validate Results**: Compare with paper trading
## Limitations
Backtesting has limitations:
- **Past Performance**: Doesn't guarantee future results
- **Market Conditions**: Markets change over time
- **Data Quality**: Results depend on data accuracy
- **Execution**: Real trading may differ from simulation
## Exporting Results
Export backtest results for analysis:
1. Click "Export Results"
2. Choose format:
- **CSV**: For spreadsheet analysis
- **PDF**: For reports
3. Save file
## Troubleshooting
**No results?**
- Check date range has data
- Verify symbol is correct
- Check strategy parameters
**Unrealistic results?**
- Verify fees are enabled
- Check slippage settings
- Review data quality

View File

@@ -0,0 +1,359 @@
# Configuration Guide
This guide explains how to configure Crypto Trader to suit your needs.
## Configuration Files
Configuration files are stored in `~/.config/crypto_trader/` following the XDG Base Directory Specification.
### Main Configuration
The main configuration file is `config.yaml`. It contains:
- Database settings
- Logging configuration
- Paper trading settings
- Update preferences
- Notification settings
- Backtesting defaults
### Logging Configuration
Logging settings are in `logging.yaml`. You can configure:
- Log levels (DEBUG, INFO, WARNING, ERROR)
- Log file location
- Log rotation settings
- Retention policies
## Exchange Configuration
### Adding Exchange API Keys via UI
1. Open the application
2. Navigate to the **Settings** tab
3. In the **Exchanges** section, click **Add Exchange**
4. Enter exchange details:
- **Exchange Name**: Name of the exchange (e.g., Coinbase)
- **API Key**: Your exchange API key
- **API Secret**: Your exchange API secret
- **Use Sandbox/Testnet**: Enable for testing
- **Read-Only Mode**: Recommended for safety (prevents trading)
- **Enabled**: Enable the exchange connection
5. Click **Save**
6. Test the connection using **Test Connection** button
### Editing Exchange Settings
1. Select an exchange from the exchanges table
2. Click **Edit Exchange**
3. Update API keys or settings as needed
4. Click **Save**
### Managing Exchanges
The Settings page provides comprehensive exchange management:
- **Status Indicators**: Each exchange shows a color-coded status indicator:
- Green: Enabled and connected
- Gray: Disabled
- Red: Error state
- **Test Connection**: Click the checkmark icon to test the connection
- You'll receive a notification with detailed connection status
- Success: Green notification
- Warning: Yellow notification with details
- Error: Red notification with error message
- **Edit Exchange**: Click the pencil icon to edit exchange settings
- **Delete Exchange**: Click the trash icon to delete (removes API keys)
- Confirmation dialog will appear
- You'll receive a success notification when deleted
### API Key Security
- API keys are encrypted before storage
- Use read-only keys when possible
- Enable IP whitelisting on your exchange account
- Never share your API keys
## Paper Trading Configuration
Paper trading settings can be configured in the Settings page:
1. Navigate to **Settings** page
2. Click on **Paper Trading** tab
3. Configure:
- **Initial Capital ($)**: Starting capital in USD (default: $100)
- **Fee Model (Exchange)**: Select which exchange's fee structure to simulate
4. Click **Save Settings**
5. You'll receive a success notification when settings are saved
### Fee Exchange Models
Choose from available exchange fee models:
| Exchange | Maker Fee | Taker Fee | Best For |
|----------|-----------|-----------|----------|
| **Default** | 0.10% | 0.10% | General testing |
| **Coinbase** | 0.40% | 0.60% | Conservative estimates |
| **Kraken** | 0.16% | 0.26% | Moderate fee simulation |
| **Binance** | 0.10% | 0.10% | Low-fee simulation |
The fee rates display shows your current maker fee, taker fee, and estimated round-trip cost.
### Resetting Paper Account
To reset your paper trading account (closes all positions and resets balance):
1. Navigate to **Settings** > **Paper Trading** tab
2. Click **Reset Paper Account** button
3. Confirm the reset in the dialog
4. All positions will be closed and balance reset to initial capital
5. You'll receive a success notification when complete
**Warning**: This action cannot be undone. All paper trading history will be preserved, but positions and balance will be reset.
## Data Provider Configuration
Data providers are used to fetch real-time pricing data for paper trading, backtesting, and ML training. They work independently of exchange integrations and don't require API keys.
### Configuring Providers
1. Navigate to **Settings** page
2. Click on **Data Providers** tab
3. Configure provider settings:
#### Primary Providers (CCXT)
Primary providers use CCXT to connect to cryptocurrency exchanges:
- **Kraken**: Default priority 1 (tried first)
- **Coinbase**: Default priority 2
- **Binance**: Default priority 3
Each provider can be:
- **Enabled/Disabled**: Toggle using the switch
- **Reordered**: Use up/down arrows to change priority order
- **Monitored**: View health status, response times, and success rates
#### Fallback Provider
**CoinGecko** is used as a fallback when primary providers are unavailable:
- Free tier API (no authentication required)
- Lower rate limits than primary providers
- Provides basic price data
#### Cache Settings
Configure caching behavior:
- **Ticker TTL (seconds)**: How long to cache ticker data (default: 2 seconds)
- **OHLCV TTL (seconds)**: How long to cache candlestick data (default: 60 seconds)
### Provider Status
The Data Providers tab shows:
- **Active Provider**: Currently used provider
- **Health Status**: Color-coded status for each provider:
- Green: Healthy and working
- Yellow: Degraded performance
- Red: Unhealthy or unavailable
- **Response Times**: Average response time for each provider
- **Success/Failure Counts**: Historical performance metrics
### Automatic Failover
The system automatically:
- Monitors provider health
- Switches to the next available provider if current one fails
- Opens circuit breakers for repeatedly failing providers
- Retries failed providers after a timeout period
### Troubleshooting Providers
If all providers fail:
1. Check your internet connection
2. Verify firewall isn't blocking connections
3. Check provider status in the Settings tab
4. Try enabling/disabling specific providers
5. Reset provider metrics if needed
## Risk Management Settings
Configure risk limits in the Settings page:
1. Navigate to **Settings** page
2. Click on **Risk Management** tab
3. Configure the following settings:
- **Max Drawdown Limit (%)**: Maximum portfolio drawdown before trading stops
- **Daily Loss Limit (%)**: Maximum daily loss percentage
- **Default Position Size (%)**: Default percentage of capital to use per trade
4. Click **Save Risk Settings**
5. You'll receive a success notification when settings are saved
Settings are validated before saving and you'll see error messages if values are invalid.
## Data Storage
Data is stored in `~/.local/share/crypto_trader/`:
- `trading.db` - Legacy file (removed)
- `historical/` - Historical market data
- `logs/` - Application logs
### Database Options
### Database Options
**PostgreSQL (Required)**:
- All production data is stored in PostgreSQL.
- Requires a running PostgreSQL instance.
- Configure connection in `config.yaml`.
**SQLite (Internal)**:
- Used internally for unit testing only.
- No configuration required for users.
## Redis Configuration
Redis is used for distributed state management, ensuring autopilot state persists across restarts and preventing duplicate instances.
### Default Settings
Redis settings are configured in `config.yaml`:
```yaml
redis:
host: "127.0.0.1"
port: 6379
db: 0
password: null # Set if Redis requires authentication
socket_connect_timeout: 5
```
### Environment Variables
You can also configure Redis via environment variables:
- `REDIS_HOST` - Redis server hostname
- `REDIS_PORT` - Redis server port
- `REDIS_PASSWORD` - Redis password (if required)
### Verifying Redis Connection
Run the verification script:
```bash
python scripts/verify_redis.py
```
## Celery Configuration (Background Tasks)
Celery handles long-running tasks like ML model training in the background.
### Starting the Worker
```bash
# From project root (with virtualenv activated)
celery -A src.worker.app worker --loglevel=info
```
Or use the helper script:
```bash
./scripts/start_all.sh
```
### Task Queues
Tasks are routed to specific queues:
- `ml_training` - Model training and data bootstrapping
- `reporting` - Report generation
- `celery` - Default queue
## ML Model Training Configuration
Configure how the autopilot ML model is trained via the Settings page.
### Training Data Configuration
Navigate to **Settings** > **Autopilot** tab to access these settings:
| Setting | Description | Recommended |
|---------|-------------|-------------|
| **Historical Data (days)** | Amount of historical data to fetch for training | 90-365 days |
| **Timeframe** | OHLCV candle timeframe for training data | 1h (balanced) |
| **Min Samples per Strategy** | Minimum samples required per strategy | 30+ |
| **Training Symbols** | Cryptocurrencies to include in training | 5-10 major coins |
### Setting Training Symbols
1. Navigate to **Settings** > **Autopilot** tab
2. In the **Training Data Configuration** section, find **Training Symbols**
3. Click to add/remove symbols from the multi-select dropdown
4. Common symbols: BTC/USD, ETH/USD, SOL/USD, XRP/USD, ADA/USD
5. Click **Save Bootstrap Config** to persist your changes
### Triggering Model Training
1. Click **Retrain Model** button in the Model Management section
2. A progress bar will appear showing:
- Data fetching progress (20-60%)
- Training progress (70-100%)
3. Training typically takes 30-60 seconds depending on data volume
4. Upon completion, the model card updates to show:
- "Global Model Trained" badge
- Number of strategies and features
- Training accuracy
- Which symbols the model was trained on
### Training Progress Persistence
Training progress persists across page navigation:
- If you navigate away during training, progress resumes on return
- The training task ID is stored in browser localStorage
- On task completion or failure, the task ID is cleared
### Model Management
- **Retrain Model**: Triggers retraining with current configuration
- **Reset Model**: Deletes all saved model files (confirmation required)
### Troubleshooting Training
If training fails:
1. Check that Redis and Celery are running
2. Review Celery worker logs: `tail -f celery.log`
3. Ensure sufficient historical data is available
4. Try reducing the number of training symbols
5. Check backend logs for error details
### Monitoring Tasks
The API provides endpoints to monitor background tasks:
- `POST /api/autopilot/intelligent/retrain` - Starts training, returns task ID
- `GET /api/autopilot/tasks/{task_id}` - Check task status
## Environment Variables
You can override configuration using environment variables:
- `CRYPTO_TRADER_CONFIG_DIR` - Custom config directory
- `CRYPTO_TRADER_DATA_DIR` - Custom data directory
- `CRYPTO_TRADER_LOG_DIR` - Custom log directory
- `DB_PASSWORD` - Database password (for PostgreSQL)
- `REDIS_HOST` - Redis server hostname
- `REDIS_PORT` - Redis server port
- `REDIS_PASSWORD` - Redis password
## Backup and Restore
### Backup
1. Stop the application and Celery workers
2. Backup PostgreSQL database
3. Copy the entire `~/.local/share/crypto_trader/` directory
4. Copy `~/.config/crypto_trader/` directory
5. (Optional) Export Redis data with `redis-cli SAVE`
### Restore
1. Stop the application
2. Restore PostgreSQL database
3. Replace the directories with your backup
4. Restart Redis, Celery worker, and application

235
docs/user_manual/faq.md Normal file
View File

@@ -0,0 +1,235 @@
# Frequently Asked Questions
Common questions about Crypto Trader.
## General Questions
### What is Crypto Trader?
Crypto Trader is a comprehensive web-based cryptocurrency trading platform for trading, backtesting, and portfolio management. It features a modern React frontend with a FastAPI backend, supporting multiple exchanges, real-time trading, paper trading, and advanced analytics.
### Is Crypto Trader free?
[Answer depends on your licensing - update as needed]
### What operating systems are supported?
The web interface works on any operating system with a modern browser:
- **Backend**: Linux, macOS, Windows (Python 3.11+)
- **Frontend**: Any OS with Chrome, Firefox, Safari, or Edge
- **Recommended**: Linux (Bluefin Linux) or macOS for development
### What Python version is required?
Python 3.11 or higher is required.
## Installation Questions
### How do I install Crypto Trader?
See the [Getting Started](getting_started.md) guide for installation instructions.
### Can I run from source?
Yes, see the [Getting Started](getting_started.md) guide. You'll need to run both the backend (FastAPI) and frontend (React) servers.
### Do I need to install TA-Lib separately?
TA-Lib is bundled in the AppImage. For source installation, you may need to install the TA-Lib C library separately.
## Trading Questions
### Is paper trading safe?
Yes, paper trading uses virtual funds and doesn't risk real money. It's perfect for testing strategies.
### How do I switch from paper trading to live trading?
1. Configure exchange API keys with trading permissions
2. Disable paper trading mode in settings
3. Start with small position sizes
4. Monitor closely
### What exchanges are supported?
Currently Coinbase is supported. The framework supports adding additional exchanges.
### Can I trade futures?
Yes, futures and leverage trading are supported. See the trading guide for details.
## Strategy Questions
### How do I create a strategy?
See the [Strategy Guide](strategies.md) for detailed instructions.
### Can I use multiple strategies at once?
Yes, you can run multiple strategies simultaneously on different symbols or timeframes.
### How do I optimize strategy parameters?
Use the backtesting optimization features. See the [Backtesting Guide](backtesting.md#parameter-optimization).
### Can I create custom strategies?
Yes, see the [Developer Guide](../developer/creating_strategies.md) for instructions.
## Backtesting Questions
### How accurate is backtesting?
Backtesting includes realistic features like slippage and fees, but past performance doesn't guarantee future results.
### How much historical data do I need?
At least 6-12 months of data is recommended for reliable backtesting.
### Can I backtest multiple strategies?
Yes, you can backtest multiple strategies and compare results.
## Autopilot Questions
### What's the difference between Pattern-Based and ML-Based Autopilot?
**Pattern-Based Autopilot** uses technical chart pattern recognition combined with sentiment analysis. It's transparent, explainable, and works immediately without training data. **ML-Based Autopilot** uses machine learning to select the best strategy based on market conditions. It's adaptive and learns from historical performance but requires training data.
See the [Trading Guide](trading.md#autopilot-modes) for detailed comparison.
### Which autopilot mode should I use?
- **Use Pattern-Based** if you want transparency, understand technical analysis, prefer explainable decisions, or want immediate setup.
- **Use ML-Based** if you want adaptive decisions, have historical trading data, don't need to understand every decision, or want maximum optimization.
See the [decision guide](trading.md#choosing-the-right-autopilot-mode) for more details.
### Can I switch between modes?
Yes, you can switch between modes at any time. Simply stop the current autopilot, select the desired mode, and start it again. You cannot run both modes simultaneously for the same symbol.
### What is auto-execution and should I enable it?
Auto-execution automatically executes trades based on autopilot signals. It's available for both modes and can be enabled/disabled independently.
**Enable auto-execution if**:
- You've tested the autopilot in paper trading mode
- You trust the autopilot's signals
- You've set appropriate risk limits
- You can monitor trades regularly
**Don't enable auto-execution if**:
- You're new to the autopilot
- You haven't tested it thoroughly
- You want to review signals before executing
- You're trading with real money and want manual control
### How do I know which mode is running?
The Dashboard shows the active mode in the status chip (e.g., "AutoPilot Active (pattern)"). You can also check the Autopilot Configuration section to see the selected mode.
### Can I use both modes simultaneously?
No, you can only run one autopilot mode at a time per symbol. However, you can run different modes on different symbols.
### What happens if I switch modes while autopilot is running?
You must stop the current autopilot before switching modes. The system will prevent starting a new mode while another is running for the same symbol.
### Does Pattern-Based Autopilot require training data?
No, Pattern-Based Autopilot works immediately without any training data. It uses predefined pattern recognition rules and sentiment analysis.
### Does ML-Based Autopilot always need training data?
Yes, ML-Based Autopilot requires training data to build its model. However, the system can bootstrap training data from historical backtests if available. You can also manually trigger model training from the Dashboard.
### How accurate are autopilot signals?
Signal accuracy depends on market conditions and the selected mode:
- **Pattern-Based**: Accuracy depends on pattern clarity and sentiment alignment
- **ML-Based**: Accuracy depends on model training quality and market regime match
Past performance doesn't guarantee future results. Always use risk management and test thoroughly in paper trading mode.
## Data Questions
### Where is data stored?
Data is stored in `~/.local/share/crypto_trader/`:
- Database: `trading.db`
- Historical data: `historical/`
- Logs: `logs/`
### How much storage do I need?
Storage depends on:
- Historical data retention
- Number of symbols tracked
- Log retention settings
Typically 1-5GB is sufficient.
### Can I use PostgreSQL instead of SQLite?
Yes, PostgreSQL is supported for advanced users. See the [Configuration Guide](configuration.md#database-options).
## Security Questions
### Are my API keys secure?
Yes, API keys are encrypted before storage using industry-standard encryption.
### Can I use read-only API keys?
Yes, read-only keys are recommended for backtesting and data collection.
### Where are API keys stored?
API keys are stored encrypted in the SQLite database.
## Performance Questions
### Why is the application slow?
Possible causes:
- Large historical datasets
- Many active strategies
- Insufficient system resources
- Database optimization needed
See [Troubleshooting](troubleshooting.md#performance-issues) for solutions.
### How can I improve performance?
- Reduce data retention
- Limit active strategies
- Optimize database
- Increase system resources
## Technical Questions
### Can I contribute to the project?
Yes! See the [Developer Guide](../developer/contributing.md) for contribution guidelines.
### How do I report bugs?
Create an issue with:
- Error description
- Steps to reproduce
- System information
- Log files
### Is there an API?
Yes, see the [API Documentation](../api/index.html) for details.
## Still Have Questions?
- Check the [User Manual](README.md)
- Review [Troubleshooting](troubleshooting.md)
- Consult [API Documentation](../api/index.html)
- See [Developer Guide](../developer/README.md)

View File

@@ -0,0 +1,157 @@
# Getting Started
This guide will help you install and set up Crypto Trader for the first time.
## Installation
### From AppImage (Recommended for Bluefin Linux)
1. Download the latest AppImage release from the releases page
2. Make it executable:
```bash
chmod +x crypto_trader-*.AppImage
```
3. Run the application:
```bash
./crypto_trader-*.AppImage
```
### From Source
1. Clone the repository:
```bash
git clone <repository-url>
cd crypto_trader
```
2. Create a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Install backend dependencies:
```bash
pip install -r requirements.txt
pip install -r backend/requirements.txt
```
5. Install frontend dependencies:
```bash
cd frontend
npm install
```
6. Run the application:
**Backend** (in one terminal):
```bash
python -m uvicorn backend.main:app --reload --port 8000
```
**Frontend** (in another terminal):
```bash
cd frontend
npm run dev
```
Access the application at: http://localhost:3000
API documentation at: http://localhost:8000/docs
## First Launch
When you first launch Crypto Trader:
1. **Configuration Setup**: The application will create configuration directories:
- `~/.config/crypto_trader/` - Configuration files
- `~/.local/share/crypto_trader/` - Data and database
- `~/.local/share/crypto_trader/logs/` - Application logs
2. **Database Initialization**:
- Ensure PostgreSQL is running and credentials are configured in `config.yaml` or via environment variables.
- Tables will be created automatically on first run.
3. **Paper Trading**: By default, the application starts in paper trading mode with $100 virtual capital.
4. **UI Overview**: The web interface contains six main pages accessible via the navigation menu:
- **Dashboard**: Overview with AutoPilot controls, system health, and real-time market data
- **Strategies**: Create, edit, delete, and manage trading strategies with full parameter configuration
- **Trading**: Manual order placement, order management, and position closing
- **Portfolio**: View portfolio performance, holdings, allocation charts, and risk metrics
- **Backtesting**: Configure and run backtests on historical data with progress tracking
- **Settings**: Manage exchanges, risk settings, alerts, alert history, and application configuration
## Adding Your First Exchange
1. Click on the **Settings** tab
2. In the **Exchanges** section, click **Add Exchange**
3. Enter your exchange name (e.g., "Coinbase")
4. Enter your API key and secret
5. Choose **Read-Only Mode** for safety (recommended for first-time setup)
6. Click **Save**
7. Test the connection using the **Test Connection** button
## Placing Your First Trade
1. Go to the **Trading** tab
2. Select an exchange and symbol from the dropdowns
3. Choose order type (Market, Limit, or Stop)
4. Select Buy or Sell
5. Enter quantity
6. For limit orders, enter your desired price
7. Click **Place Order**
The order will execute in paper trading mode by default. You can view your positions in the **Open Positions** table and order history in the **Order History** section.
## Using Autopilot
The Autopilot feature provides autonomous trading signal generation with two modes:
1. **Pattern-Based Autopilot**: Uses technical pattern recognition and sentiment analysis
- Transparent and explainable
- Works immediately without training data
- Best for users who want to understand every decision
2. **ML-Based Autopilot**: Uses machine learning to select optimal strategies
- Adaptive and learns from performance
- Requires training data
- Best for users who want data-driven optimization
**Quick Start**:
1. Go to the **Dashboard** page
2. In the **Autopilot Configuration** section, select your preferred mode
3. Choose a symbol (e.g., BTC/USD)
4. Optionally enable **Auto-Execute** (start with paper trading first!)
5. Click **Start AutoPilot**
See the [Trading Guide](trading.md#autopilot-modes) for detailed information about both modes and when to use each.
## Next Steps
1. [Configure your exchanges](configuration.md#exchanges)
2. [Set up your first strategy](strategies.md#creating-a-strategy)
3. [Try paper trading](trading.md#paper-trading)
4. [Try autopilot](trading.md#autopilot-modes)
5. [Run a backtest](backtesting.md#running-a-backtest)
## System Requirements
- **Python**: 3.11 or higher
- **Operating System**: Linux (Bluefin Linux recommended), macOS, Windows
- **Memory**: 2GB RAM minimum, 4GB recommended
- **Storage**: 500MB for application, additional space for historical data
- **Network**: Internet connection for real-time data and trading
- **Database**: PostgreSQL 12 or higher (required)
## Getting Help
- Check the [FAQ](faq.md) for common questions
- Review the [Troubleshooting](troubleshooting.md) guide
- Consult the [API Documentation](../api/index.html) for advanced usage

View File

@@ -0,0 +1,134 @@
# Portfolio Management
This guide explains the Portfolio view and how to interpret portfolio metrics.
## Portfolio View Features
The Portfolio page provides a comprehensive view of your trading portfolio with three main tabs:
### Overview Tab
#### Summary Cards
At the top of the page, you'll see four summary cards:
- **Current Value**: Current portfolio value (cash + positions)
- **Unrealized P&L**: Profit/loss on open positions (color-coded: green for profit, red for loss)
- **Realized P&L**: Profit/loss from closed trades
- **Daily Change**: Percentage change in portfolio value today
#### Portfolio Value History Chart
An equity curve chart showing:
- Portfolio value over time (last 30 days by default)
- P&L line showing profit/loss progression
- Interactive tooltips showing exact values on hover
#### Risk Metrics
A grid of risk metrics cards showing:
- **Sharpe Ratio**: Risk-adjusted return measure
- **Sortino Ratio**: Downside risk-adjusted return
- **Max Drawdown**: Largest historical decline (percentage)
- **Win Rate**: Percentage of profitable trades
- **Total Return**: Overall return percentage
#### Holdings
All open positions displayed as cards showing:
- **Symbol**: Trading pair
- **Quantity**: Amount held (8 decimal precision)
- **Entry Price**: Average entry price
- **Current Price**: Latest market price
- **Value**: Current position value
- **Unrealized P&L**: Profit/loss with percentage change
- **Realized P&L**: Profit/loss from closed portions
- **Close Position Button**: Close the position directly from the card
Each position card is color-coded with a chip showing the P&L percentage.
### Allocations Tab
A pie chart visualization showing:
- Portfolio allocation by asset
- Percentage breakdown for each holding
- Interactive tooltips with exact dollar values
- Color-coded segments for easy identification
This helps you visualize your portfolio distribution and identify over-concentration in specific assets.
### Reports & Export Tab
Export functionality for:
- **Export Trades**: Download all trading history as CSV
- **Export Portfolio**: Download current portfolio holdings as CSV
- **Tax Reporting**: Generate tax reports using FIFO or LIFO methods
### Risk Metrics
- **Current Drawdown**: Current decline from peak value
- **Max Drawdown**: Largest historical decline
- **Sharpe Ratio**: Risk-adjusted return measure
- **Sortino Ratio**: Downside risk-adjusted return
- **Win Rate**: Percentage of profitable trades
### Performance Chart
The equity curve chart shows portfolio value over time, helping you visualize performance trends.
## Understanding Metrics
### Sharpe Ratio
Measures risk-adjusted returns. Higher values indicate better risk-adjusted performance:
- **> 1**: Good
- **> 2**: Very good
- **> 3**: Excellent
### Sortino Ratio
Similar to Sharpe but only considers downside volatility. Better for asymmetric return distributions.
### Drawdown
Maximum peak-to-trough decline. Lower is better:
- **< 10%**: Low risk
- **10-20%**: Moderate risk
- **> 20%**: High risk
## Closing Positions from Portfolio
You can close positions directly from the Portfolio page:
1. Navigate to the **Portfolio** page
2. In the **Overview** tab, find the position card you want to close
3. Click **Close Position** button on the card
4. In the dialog:
- Choose order type (Market or Limit)
- For limit orders, enter the limit price
5. Click **Close Position** to confirm
The position will be closed and removed from your holdings. You'll receive a notification when the order is filled.
## Data Freshness
The Portfolio page shows a data freshness indicator in the header showing when the data was last updated. The page automatically refreshes every 5 seconds to show the latest data.
## Exporting Data
### Export Trades
1. Navigate to **Portfolio** > **Reports & Export** tab
2. Click **Export Trades CSV** button
3. The file will download with all your trading history
### Export Portfolio
1. Navigate to **Portfolio** > **Reports & Export** tab
2. Click **Export Portfolio CSV** button
3. The file will download with current holdings
### Tax Reports
1. Navigate to **Portfolio** > **Reports & Export** tab
2. Select tax method (FIFO or LIFO)
3. Enter tax year
4. Optionally filter by symbol
5. Click **Generate Tax Report**
6. The CSV file will download with tax reporting information

View File

@@ -0,0 +1,120 @@
# Reporting Guide
Export your trading data and generate reports.
## Export Formats
### CSV Export
Export data to CSV for spreadsheet analysis:
- **Trades**: All executed trades
- **Orders**: Order history
- **Positions**: Current and historical positions
- **Portfolio**: Portfolio snapshots
- **Backtest Results**: Backtesting data
### PDF Reports
Generate comprehensive PDF reports:
- **Trading Report**: Summary of trading activity
- **Performance Report**: Portfolio performance analysis
- **Tax Report**: Tax reporting information
## Exporting Trades
1. Navigate to Reporting view
2. Select "Export Trades"
3. Choose date range:
- **Start Date**: Beginning of period
- **End Date**: End of period
4. Select filters:
- **Symbol**: Specific trading pair
- **Paper Trading**: Include/exclude paper trades
- **Exchange**: Specific exchange
5. Choose format: CSV or PDF
6. Click "Export"
7. Save file
## Exporting Portfolio
1. Select "Export Portfolio"
2. Choose data:
- **Current Positions**: Open positions
- **Position History**: Historical positions
- **Performance Metrics**: Analytics data
3. Select format
4. Click "Export"
5. Save file
## Tax Reporting
Generate tax reports for accounting:
1. Navigate to Reporting > Tax Report
2. Select tax year
3. Choose method:
- **FIFO**: First In, First Out
- **LIFO**: Last In, First Out
- **Specific Identification**: Choose specific lots
4. Configure settings:
- **Currency**: Reporting currency
- **Include Fees**: Include trading fees
- **Include Paper Trades**: Optional
5. Generate report
6. Review and export
### Tax Report Contents
- **Realized Gains/Losses**: Profit/loss from closed trades
- **Trade Summary**: All trades in period
- **Cost Basis**: Purchase costs
- **Proceeds**: Sale proceeds
- **Gain/Loss**: Net gain or loss
## Scheduled Reports
Set up automatic report generation:
1. Navigate to Settings > Scheduled Reports
2. Click "Add Schedule"
3. Configure:
- **Report Type**: Trades, Portfolio, Tax
- **Frequency**: Daily, Weekly, Monthly
- **Format**: CSV, PDF
- **Email**: Optional email delivery
4. Save schedule
## Performance Reports
Generate performance analysis reports:
- **Period Performance**: Returns for time period
- **Risk Metrics**: Sharpe, Sortino, drawdown
- **Trade Analysis**: Win rate, average win/loss
- **Asset Performance**: Performance by asset
- **Monthly Breakdown**: Performance by month
## Custom Reports
Create custom reports:
1. Select "Custom Report"
2. Choose data sources:
- Trades
- Positions
- Portfolio snapshots
- Backtest results
3. Select metrics to include
4. Configure filters
5. Generate report
## Best Practices
1. **Regular Exports**: Export data regularly for backup
2. **Tax Records**: Keep detailed tax reports
3. **Performance Tracking**: Generate monthly performance reports
4. **Data Backup**: Export before major updates
5. **Record Keeping**: Maintain organized export files

View File

@@ -0,0 +1,286 @@
# Strategy Guide
Learn how to create, configure, and manage trading strategies in Crypto Trader.
## What is a Strategy?
A trading strategy is a set of rules that determine when to buy and sell cryptocurrencies. Strategies analyze market data and generate trading signals.
## Pre-built Strategies
Crypto Trader includes several pre-built strategies:
### RSI Strategy
Uses the Relative Strength Index to identify overbought and oversold conditions.
- **Buy Signal**: RSI below 30 (oversold)
- **Sell Signal**: RSI above 70 (overbought)
- **Parameters**: RSI period (default: 14)
### MACD Strategy
Uses Moving Average Convergence Divergence for trend following.
- **Buy Signal**: MACD crosses above signal line
- **Sell Signal**: MACD crosses below signal line
- **Parameters**: Fast period, slow period, signal period
### Moving Average Crossover
Uses two moving averages to identify trend changes.
- **Buy Signal**: Short MA crosses above long MA
- **Sell Signal**: Short MA crosses below long MA
- **Parameters**: Short MA period, long MA period
### Confirmed Strategy (Multi-Indicator)
Requires multiple indicators (RSI, MACD, Moving Average) to align before generating signals. This significantly reduces false signals by requiring confirmation from 2-3 indicators.
- **Buy Signal**: When majority of indicators agree on buy signal
- **Sell Signal**: When majority of indicators agree on sell signal
- **Parameters**:
- RSI period, oversold, overbought thresholds
- MACD fast, slow, signal periods
- MA fast, slow periods, MA type (SMA/EMA)
- Minimum confirmations required (default: 2)
- Which indicators to require (RSI, MACD, MA)
### Divergence Strategy
Detects price vs. indicator divergences which are powerful reversal signals. Works exceptionally well in ranging markets.
- **Buy Signal**: Bullish divergence (price makes lower low, indicator makes higher low)
- **Sell Signal**: Bearish divergence (price makes higher high, indicator makes lower high)
- **Parameters**:
- Indicator type: RSI or MACD (default: RSI)
- Lookback period for swing detection (default: 20)
- Minimum swings required (default: 2)
- Minimum confidence threshold (default: 0.5)
### Bollinger Bands Mean Reversion
Trades mean reversion using Bollinger Bands. Buys at lower band in uptrends, exits at middle band. Works well in ranging markets.
- **Buy Signal**: Price touches lower Bollinger Band in uptrend
- **Sell Signal**: Price reaches middle band (profit target) or touches upper band (stop loss)
- **Parameters**:
- Bollinger Bands period (default: 20)
- Standard deviation multiplier (default: 2.0)
- Trend filter enabled (default: True)
- Trend MA period (default: 50)
- Entry threshold (how close to band, default: 0.95)
- Exit threshold (when to take profit, default: 0.5)
### Consensus Strategy (Ensemble)
Combines signals from multiple strategies with voting mechanism. Only executes when multiple strategies agree, improving signal quality through ensemble methods.
- **Buy Signal**: When minimum number of strategies agree on buy
- **Sell Signal**: When minimum number of strategies agree on sell
- **Parameters**:
- Strategy names to include (None = all available)
- Minimum consensus count (default: 2)
- Use performance-based weights (default: True)
- Minimum weight threshold (default: 0.3)
- Strategies to exclude
## Strategy Management Page
The Strategy Management page provides a comprehensive interface for creating, editing, and managing all your trading strategies.
### Accessing Strategy Management
1. Click on **Strategies** in the navigation menu
2. You'll see a table listing all your strategies with:
- Strategy name and description
- Strategy type
- Trading symbol
- Timeframes
- Status (Enabled/Disabled)
- Paper Trading mode
- Action buttons (Start, Stop, Edit, Delete)
## Creating a Strategy
1. Navigate to the **Strategies** page
2. Click **Create Strategy** button
3. The strategy dialog has three tabs:
**Basic Settings Tab**:
- **Name**: Give your strategy a descriptive name (required)
- **Description**: Optional description
- **Strategy Type**: Select from RSI, MACD, Moving Average, Confirmed, Divergence, Bollinger Mean Reversion, Consensus, DCA, Grid, or Momentum
- **Symbol**: Trading pair (e.g., BTC/USD)
- **Exchange**: Select the exchange (required)
- **Timeframes**: Select one or more timeframes (1m, 5m, 15m, 30m, 1h, 4h, 1d)
- **Paper Trading Mode**: Toggle for paper trading (recommended for testing)
**Parameters Tab**:
- Configure strategy-specific parameters
- Parameters vary by strategy type
- Default values are provided for all parameters
- See strategy-specific sections below for parameter details
**Risk Settings Tab**:
- **Position Size (%)**: Percentage of capital to use per trade
- **Stop Loss (%)**: Maximum loss percentage before exit
- **Take Profit (%)**: Profit target percentage
- **Max Position Size**: Optional maximum position size limit
4. Click **Create** to save the strategy
The strategy will appear in the strategies table. New strategies start as **Disabled** - you must enable and start them manually.
## Managing Strategies
### Starting a Strategy
1. Find the strategy in the table
2. Click the **Start** button (green play icon)
3. The strategy status will change to **Enabled** and it will begin generating signals
### Stopping a Strategy
1. Find the running strategy in the table
2. Click the **Stop** button (red stop icon)
3. The strategy will stop generating new signals
### Editing a Strategy
1. Click the **Edit** button (pencil icon) for the strategy
2. Modify any settings in the dialog
3. Click **Update** to save changes
**Note**: Strategy type cannot be changed after creation. You must create a new strategy with a different type.
### Deleting a Strategy
1. Click the **Delete** button (red trash icon)
2. Confirm the deletion in the dialog
3. The strategy and all its configuration will be permanently deleted
## Strategy Types
### DCA (Dollar Cost Averaging)
Invests a fixed amount at regular intervals (daily, weekly, or monthly). Ideal for long-term accumulation.
**Parameters:**
- Amount: Fixed investment amount per interval
- Interval: Daily, weekly, or monthly
- Target Allocation: Target portfolio allocation percentage
### Grid Trading
Places buy orders at lower price levels and sell orders at higher levels. Profits from price oscillations.
**Parameters:**
- Grid Spacing: Percentage between grid levels
- Number of Levels: Grid levels above and below center price
- Profit Target: Profit percentage to take
### Momentum
Trades based on price momentum with volume confirmation. Enters on strong upward momentum, exits on reversal.
**Parameters:**
- Lookback Period: Period for momentum calculation
- Momentum Threshold: Minimum momentum to enter
- Volume Threshold: Volume increase multiplier for confirmation
- Exit Threshold: Momentum reversal threshold
## Strategy Configuration
### Basic Settings
- **Name**: Descriptive name for your strategy
- **Symbol**: Trading pair (e.g., BTC/USD)
- **Timeframe**: Data timeframe (1m, 5m, 15m, 1h, 4h, 1d)
- **Enabled**: Turn strategy on/off
### Parameters
Each strategy has specific parameters:
- **RSI Strategy**: RSI period, overbought threshold, oversold threshold
- **MACD Strategy**: Fast period, slow period, signal period
- **Moving Average**: Short period, long period, MA type (SMA/EMA)
- **Confirmed Strategy**: RSI, MACD, and MA parameters, minimum confirmations
- **Divergence Strategy**: Indicator type, lookback period, confidence threshold
- **Bollinger Mean Reversion**: Period, std dev, trend filter settings
- **Consensus Strategy**: Strategy selection, consensus threshold, weighting options
### Risk Settings
- **Position Size**: Amount to trade per signal
- **Max Position**: Maximum position size
- **Stop Loss**: Percentage, price level, or ATR-based dynamic stop
- **ATR-based stops**: Automatically adjust stop distance based on market volatility
- **ATR Multiplier**: Multiplier for ATR calculation (default: 2.0)
- **ATR Period**: Period for ATR calculation (default: 14)
- **Take Profit**: Profit target
- **Trend Filter**: Optional ADX-based trend filter to avoid trading in choppy markets
## Multi-Timeframe Strategies
Strategies can use multiple timeframes:
1. Primary timeframe for signal generation
2. Higher timeframes for trend confirmation
3. Lower timeframes for entry timing
## Strategy Scheduling
Strategies can be scheduled to run:
- **Continuous**: Runs on every new candle
- **Time-based**: Runs at specific times
- **Condition-based**: Runs when conditions are met
## Managing Strategies
### Enabling/Disabling
- Toggle strategy on/off without deleting
- Disabled strategies don't generate signals
- Useful for testing multiple strategies
### Editing Strategies
1. Select the strategy
2. Click "Edit"
3. Modify parameters
4. Save changes
### Deleting Strategies
1. Select the strategy
2. Click "Delete"
3. Confirm deletion
## Strategy Performance
Monitor strategy performance:
- **Win Rate**: Percentage of profitable trades
- **Total Return**: Overall return
- **Sharpe Ratio**: Risk-adjusted return
- **Max Drawdown**: Largest peak-to-trough decline
- **Number of Trades**: Total trades executed
## Best Practices
1. **Backtest First**: Always backtest strategies before live trading
2. **Start Small**: Use small position sizes initially
3. **Monitor Performance**: Regularly review strategy results
4. **Adjust Parameters**: Optimize based on performance
5. **Use Paper Trading**: Test changes in paper trading first
## Creating Custom Strategies
For advanced users, you can create custom strategies:
1. See [Developer Guide](../developer/creating_strategies.md)
2. Extend the `BaseStrategy` class
3. Implement signal generation logic
4. Register your strategy

332
docs/user_manual/trading.md Normal file
View File

@@ -0,0 +1,332 @@
# Trading Guide
This guide explains how to trade cryptocurrencies using Crypto Trader.
## Trading Modes
### Paper Trading
Paper trading allows you to practice trading with virtual funds without risking real money.
- Default starting capital: $100 USD
- All trades are simulated
- Perfect for testing strategies
- No real money at risk
### Live Trading
Live trading executes real orders on connected exchanges.
- Requires exchange API keys with trading permissions
- Real money is at risk
- Always test strategies in paper trading first
- Use risk management features
## Autopilot Modes
The Autopilot feature provides autonomous trading signal generation with two distinct modes, each optimized for different use cases and user preferences.
### Pattern-Based Autopilot
**What it does**: Detects technical chart patterns (Head & Shoulders, triangles, wedges, etc.) and combines them with sentiment analysis to generate trading signals.
**How it works**:
- Uses geometric pattern recognition to identify 40+ chart patterns
- Analyzes news headlines using FinBERT for market sentiment
- Generates signals when patterns align with sentiment
- Rule-based logic: Pattern + Sentiment Alignment = Signal
**Best for**:
- Users who want transparency and explainable decisions
- Users who understand technical analysis
- Users who prefer immediate setup without training data
- Users who want lightweight, fast execution
**Key Features**:
- ✅ Transparent and explainable - you can see exactly why each signal was generated
- ✅ No training data required - works immediately
- ✅ Fast and lightweight - minimal resource usage
- ✅ Pattern recognition for 40+ chart patterns
- ✅ Real-time sentiment analysis
**Tradeoffs**:
- ❌ Less adaptive to market changes (fixed rules)
- ❌ Requires both pattern and sentiment alignment for signals
### ML-Based Autopilot
**What it does**: Uses machine learning to analyze market conditions and automatically select the best trading strategy from available strategies.
**How it works**:
- Analyzes current market conditions (volatility, trend, volume, etc.)
- Uses ML model trained on historical performance data
- Selects optimal strategy based on market regime
- Can auto-execute trades when confidence is high
**Best for**:
- Users who want adaptive, data-driven decisions
- Users who don't need to understand every decision
- Advanced users seeking performance optimization
- Users with sufficient historical data for training
**Key Features**:
- ✅ Adapts to market conditions automatically
- ✅ Learns from historical performance
- ✅ Optimizes strategy selection
- ✅ Market condition analysis
- ✅ Performance tracking and learning
**Tradeoffs**:
- ❌ Requires training data (needs historical trades)
- ❌ Less transparent (ML model decisions are less explainable)
- ❌ More complex setup (model training required)
### Choosing the Right Autopilot Mode
Use this decision guide to select the appropriate mode:
**Choose Pattern-Based if**:
- You want to understand every trading decision
- You prefer transparent, explainable logic
- You want immediate setup without waiting for training data
- You understand technical analysis patterns
- You want lightweight, fast execution
**Choose ML-Based if**:
- You want adaptive, data-driven decisions
- You have sufficient historical trading data
- You want the system to learn and optimize automatically
- You don't need to understand every decision
- You want maximum performance optimization
### Mode Comparison
| Feature | Pattern-Based | ML-Based |
|---------|--------------|----------|
| **Transparency** | High - All decisions explainable | Low - ML model decisions less transparent |
| **Adaptability** | Low - Fixed rules | High - Learns and adapts |
| **Setup Time** | Immediate - No setup required | Requires training data collection |
| **Resource Usage** | Low - Lightweight | Medium - ML model overhead |
| **Training Data** | Not required | Required |
| **Pattern Recognition** | Yes (40+ patterns) | No (uses strategies) |
| **Sentiment Analysis** | Yes (FinBERT) | No |
| **ML Strategy Selection** | No | Yes |
| **Auto-Execution** | Configurable | Configurable |
### Auto-Execution
Both autopilot modes support auto-execution, which can be enabled independently of mode selection.
**What is Auto-Execution?**
- When enabled, the autopilot will automatically execute trades based on generated signals
- Trades are executed according to risk management rules
- You can monitor all auto-executed trades in the Trading page
**Safety Considerations**:
- ⚠️ **Warning**: Auto-execution will automatically execute trades with real money (if not in paper trading mode)
- Always test in paper trading mode first
- Set appropriate risk limits before enabling
- Monitor auto-executed trades regularly
- Start with small position sizes
**How to Enable/Disable**:
1. Go to the Dashboard page
2. Find the Autopilot Configuration section
3. Toggle the "Auto-Execute" switch
4. Confirm when prompted (first time only)
**Monitoring Auto-Executed Trades**:
- All auto-executed trades appear in the Trading page
- Check the Order History tab to review executed trades
- Monitor positions in the Positions tab
- Review autopilot status in the Dashboard
### Switching Between Modes
You can switch between autopilot modes at any time:
1. Stop the current autopilot (if running)
2. Select the desired mode in the Autopilot Configuration section
3. Configure mode-specific settings
4. Start the autopilot in the new mode
**Note**: You cannot run both modes simultaneously for the same symbol. Stopping one mode before starting another is required.
## Manual Trading Interface
The Trading page provides a comprehensive interface for manual order placement and management.
### Accessing the Trading Page
1. Click on **Trading** in the navigation menu
2. The page displays:
- Account balance and summary
- Open positions (card view)
- Active orders table
- Order history table
### Placing Orders
#### Market Orders
Market orders execute immediately at the current market price.
1. Click **Place Order** button
2. In the order dialog:
- Select **Exchange** from dropdown
- Select **Symbol** (e.g., BTC/USD)
- Choose **Side** (Buy or Sell)
- Select **Order Type**: Market
- Enter **Quantity**
3. Click **Place Order**
The order will execute immediately. You'll see a success notification and the order will appear in the Active Orders table.
#### Limit Orders
Limit orders execute only when the price reaches your specified limit.
1. Click **Place Order** button
2. Select **Order Type**: Limit
3. Enter **Price** (required for limit orders)
4. Enter **Quantity**
5. Click **Place Order**
The order will appear in Active Orders and execute when the price reaches your limit.
#### Advanced Order Types
**Stop Loss**: Automatically sells if price drops below threshold
- Select "Stop Loss" order type
- Enter stop price
- Enter quantity
**Take Profit**: Automatically sells when profit target is reached
- Select "Take Profit" order type
- Enter target price
- Enter quantity
**Trailing Stop**: Adjusts stop price as price moves favorably
- Select "Trailing Stop" order type
- Enter initial stop price
- Enter quantity
**OCO (One-Cancels-Other)**: Places two orders, one cancels the other when filled
- Select "OCO" order type
- Configure both orders in advanced options
**Iceberg**: Large order split into smaller visible orders
- Select "Iceberg" order type
- Configure in advanced options
## Managing Positions
### Viewing Positions
Positions can be viewed in two places:
1. **Trading Page - Positions Tab**:
- Card-based view showing all open positions
- Each card displays:
- Symbol
- Quantity
- Entry price
- Current price
- Unrealized P&L (with percentage)
- Realized P&L
- Position value
2. **Portfolio Page**:
- Detailed portfolio view with allocation charts
- Same position cards with close functionality
### Closing Positions
1. Navigate to **Trading** page and select **Positions** tab, or go to **Portfolio** page
2. Find the position card you want to close
3. Click **Close Position** button on the card
4. In the close dialog:
- Choose order type (Market or Limit)
- For limit orders, enter the limit price
5. Click **Close Position** to confirm
The position will be closed and removed from your open positions. You'll receive a notification when the order is filled.
## Order Management
### Viewing Orders
The Trading page has three tabs for order management:
1. **Positions Tab**: Shows all open positions with detailed P&L information
2. **Active Orders Tab**: Displays all pending, open, and partially filled orders
- Shows order details: time, symbol, side, type, quantity, price, fill status
- Real-time updates as orders are filled or canceled
3. **Order History Tab**: Shows completed, canceled, rejected, and expired orders
- Includes filled quantity, average fill price, and fees
- Limited to most recent 50 orders
### Canceling Orders
1. Navigate to **Trading** page
2. Select **Active Orders** tab
3. Find the order you want to cancel
4. Click the **Cancel** button (red X icon) in the Actions column
The order will be canceled immediately and moved to Order History. You'll receive a notification confirming the cancellation.
### Order Status Indicators
Orders are color-coded by status:
- **Green (Filled)**: Successfully executed
- **Yellow (Pending/Open/Partially Filled)**: Waiting to be filled
- **Red (Cancelled/Rejected/Expired)**: Failed or canceled orders
## Best Practices
1. **Start with Paper Trading**: Always test strategies first
2. **Use Risk Management**: Set stop-losses and position limits
3. **Monitor Positions**: Regularly check your open positions
4. **Start Small**: Begin with small position sizes
5. **Keep Records**: Review your trading history regularly
## Fees
Trading fees are automatically calculated and deducted:
- **Maker fees**: Lower fees for limit orders that add liquidity
- **Taker fees**: Higher fees for market orders
- Fees vary by exchange
### Paper Trading Fee Simulation
For paper trading, you can simulate different exchange fee structures:
1. Go to **Settings → Paper Trading**
2. Select **Fee Model (Exchange)** from the dropdown:
- **Default**: 0.1% maker / 0.1% taker
- **Coinbase**: 0.4% maker / 0.6% taker
- **Kraken**: 0.16% maker / 0.26% taker
- **Binance**: 0.1% maker / 0.1% taker
3. Click **Save Settings**
The current fee rates display shows:
- Maker fee rate
- Taker fee rate
- Estimated round-trip cost (buy + sell)
This helps you understand how fees impact your trading strategy profitability before going live.
## Troubleshooting
**Order not executing?**
- Check exchange connection
- Verify API permissions
- Check account balance
- Review order parameters
**Position not showing?**
- Refresh the portfolio view
- Check database connection
- Review application logs

View File

@@ -0,0 +1,254 @@
# Troubleshooting Guide
Common issues and solutions for Crypto Trader.
## Application Won't Start
### Issue: Backend server won't start
**Solutions:**
1. Check Python version (requires 3.11+)
2. Verify all dependencies are installed: `pip install -r requirements.txt && pip install -r backend/requirements.txt`
3. Check if port 8000 is already in use: `lsof -i :8000` (Linux/macOS) or `netstat -ano | findstr :8000` (Windows)
4. Try running manually: `python -m uvicorn backend.main:app --reload --port 8000`
5. Check log files in `~/.local/share/crypto_trader/logs/`
6. Verify database permissions
### Issue: Frontend won't start
**Solutions:**
1. Verify Node.js is installed (v18+): `node --version`
2. Install dependencies: `cd frontend && npm install`
3. Check if port 3000 is already in use
4. Try running manually: `cd frontend && npm run dev`
5. Clear node_modules and reinstall: `rm -rf node_modules package-lock.json && npm install`
6. Check browser console for errors
### Issue: Cannot connect to backend from frontend
**Solutions:**
1. Verify backend is running on http://localhost:8000
2. Check API endpoint in browser: http://localhost:8000/docs
3. Check CORS settings in backend
4. Verify API client URL in frontend code
5. Check browser console for CORS errors
6. Verify firewall isn't blocking connections
### Issue: Import errors
**Solutions:**
1. Reinstall dependencies: `pip install -r requirements.txt`
2. Verify virtual environment is activated
3. Check Python path configuration
4. Reinstall problematic packages
## Exchange Connection Issues
### Issue: Cannot connect to exchange
**Solutions:**
1. Verify API keys are correct
2. Check API key permissions
3. Verify internet connection
4. Check exchange status (may be down)
5. Review exchange rate limits
6. Check firewall settings
### Issue: API key errors
**Solutions:**
1. Verify API keys are valid
2. Check key permissions (read-only vs trading)
3. Regenerate API keys if needed
4. Verify IP whitelist settings
5. Check key expiration dates
## Trading Issues
### Issue: Orders not executing
**Solutions:**
1. Check account balance
2. Verify API permissions include trading
3. Check order parameters (price, quantity)
4. Review exchange order limits
5. Check if market is open
6. Verify order type is supported
### Issue: Positions not updating
**Solutions:**
1. Refresh portfolio view
2. Check database connection
3. Verify exchange connection
4. Review application logs
5. Restart application
## Data Issues
### Issue: Missing historical data
**Solutions:**
1. Check data collection is enabled
2. Verify exchange connection
3. Check data storage permissions
4. Review data retention settings
5. Manually trigger data collection
### Issue: Incorrect price data
**Solutions:**
1. Verify exchange connection
2. Check data source
3. Review data quality settings
4. Clear and reload data
5. Check for data gaps
## Performance Issues
### Issue: Application is slow
**Solutions:**
1. Check system resources (CPU, memory)
2. Reduce historical data retention
3. Optimize database (VACUUM ANALYZE)
4. Close unnecessary strategies
5. Reduce chart data points
6. Check for memory leaks in logs
### Issue: High memory usage
**Solutions:**
1. Reduce data retention period
2. Limit number of active strategies
3. Reduce chart history
4. Clear old logs
5. Restart application periodically
## Database Issues
### Issue: Database errors
**Solutions:**
1. Check database connection to PostgreSQL
2. Verify disk space available
3. Check database user permissions
4. Backup and restore database if corrupted
5. Review database logs
### Issue: Database connection failed
**Solutions:**
1. Check if PostgreSQL service is running
2. Verify connection URL in `config.yaml`
3. Check network connectivity to database host
4. Verify database user credentials and permissions
5. Check PostgreSQL logs
## Configuration Issues
### Issue: Settings not saving
**Solutions:**
1. Check config directory permissions
2. Verify config file is writable
3. Check disk space
4. Review application logs
5. Manually edit config file if needed
### Issue: Configuration errors
**Solutions:**
1. Validate YAML syntax
2. Check for invalid values
3. Review default configuration
4. Reset to defaults if needed
5. Check configuration file encoding
## Web UI Issues
### Issue: Page not loading or blank screen
**Solutions:**
1. Check browser console for JavaScript errors (F12)
2. Clear browser cache and reload
3. Verify frontend server is running
4. Check network tab for failed API requests
5. Try a different browser
6. Check if backend is accessible
### Issue: WebSocket connection not working
**Solutions:**
1. Check WebSocket status indicator in Dashboard
2. Verify backend WebSocket endpoint is accessible
3. Check browser console for WebSocket errors
4. Verify firewall/proxy settings allow WebSocket connections
5. Try refreshing the page
6. Check backend logs for WebSocket errors
### Issue: Real-time updates not appearing
**Solutions:**
1. Check WebSocket connection status
2. Verify WebSocket is connected (green indicator)
3. Refresh the page
4. Check browser console for errors
5. Verify backend is sending WebSocket messages
6. Check network tab for WebSocket connection
### Issue: Forms not submitting
**Solutions:**
1. Check browser console for validation errors
2. Verify all required fields are filled
3. Check for error messages in the form
4. Verify API endpoint is accessible
5. Check network tab for failed requests
6. Look for Snackbar error notifications
## Strategy Issues
### Issue: Strategy not generating signals
**Solutions:**
1. Verify strategy is enabled (check Strategies page)
2. Check strategy parameters
3. Verify data is available
4. Check strategy logs
5. Review signal generation logic
6. Check Operations Panel for running strategies
### Issue: Strategy errors
**Solutions:**
1. Check strategy parameters in Strategy Management page
2. Verify data availability
3. Review strategy code
4. Check application logs
5. Test with different parameters
6. Check error notifications in UI
## Getting Help
If you continue to experience issues:
1. **Check Logs**: Review application logs in `~/.local/share/crypto_trader/logs/`
2. **Review Documentation**: Check user manual and API docs
3. **Check FAQ**: See [FAQ](faq.md) for common questions
4. **Report Issues**: Create an issue with:
- Error messages
- Steps to reproduce
- System information
- Log files
## Log Files
Log files are located in `~/.local/share/crypto_trader/logs/`:
- `app.log`: Main application log
- `trading.log`: Trading operations
- `errors.log`: Error messages
- `debug.log`: Debug information (if enabled)
Review logs for detailed error information.

53
frontend/README.md Normal file
View File

@@ -0,0 +1,53 @@
# Crypto Trader Frontend
Modern React frontend for the Crypto Trader application.
## Setup
```bash
npm install
```
## Development
```bash
npm run dev
```
Access at: http://localhost:3000
## Build
```bash
npm run build
```
## Environment Variables
Create `.env` file:
```env
VITE_API_URL=http://localhost:8000
VITE_WS_URL=ws://localhost:8000/ws/
```
## Tech Stack
- **React 18** - UI framework
- **TypeScript** - Type safety
- **Material-UI** - Component library
- **React Query** - Data fetching
- **React Router** - Routing
- **Recharts** - Charts
- **Vite** - Build tool
## Project Structure
```
src/
├── api/ # API client functions
├── components/ # Reusable components
├── hooks/ # Custom React hooks
├── pages/ # Page components
└── types/ # TypeScript types
```

View File

@@ -0,0 +1,89 @@
import { test, expect } from '@playwright/test'
test.describe('Dashboard Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
})
test('loads dashboard page', async ({ page }) => {
await expect(page).toHaveTitle(/FXQ One|Crypto Trader/i)
})
test('displays main sections', async ({ page }) => {
// Check for main dashboard elements
await expect(page.getByRole('heading', { level: 4 })).toBeVisible()
// Wait for content to load
await page.waitForLoadState('networkidle')
// Check for navigation elements
await expect(page.getByRole('navigation')).toBeVisible()
})
test('displays autopilot configuration section', async ({ page }) => {
await page.waitForLoadState('networkidle')
// Look for autopilot related elements
await expect(page.getByText(/autopilot/i).first()).toBeVisible()
})
test('navigation works correctly', async ({ page }) => {
// Navigate to different pages
await page.click('text=Trading')
await expect(page).toHaveURL(/.*trading/i)
await page.click('text=Portfolio')
await expect(page).toHaveURL(/.*portfolio/i)
await page.click('text=Strategies')
await expect(page).toHaveURL(/.*strateg/i)
await page.click('text=Settings')
await expect(page).toHaveURL(/.*settings/i)
// Go back to dashboard
await page.click('text=Dashboard')
await expect(page).toHaveURL(/.*\/$/)
})
test('displays real-time status indicators', async ({ page }) => {
await page.waitForLoadState('networkidle')
// Look for status indicators (chips, badges, etc.)
const statusChips = page.locator('.MuiChip-root')
await expect(statusChips.first()).toBeVisible({ timeout: 10000 })
})
})
test.describe('Dashboard - Autopilot Controls', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('shows autopilot start button', async ({ page }) => {
const startButton = page.getByRole('button', { name: /start.*autopilot/i })
// Button should be visible
await expect(startButton).toBeVisible({ timeout: 10000 })
})
test('symbol selection is available', async ({ page }) => {
// Look for symbol selector (autocomplete or select)
const symbolInput = page.locator('[data-testid="autopilot-symbols"], .MuiAutocomplete-root, input[placeholder*="symbol" i]').first()
await expect(symbolInput).toBeVisible({ timeout: 10000 })
})
})
test.describe('Dashboard - Charts', () => {
test('chart grid displays', async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
// Wait for charts to potentially load
await page.waitForTimeout(2000)
// Look for chart container
const chartArea = page.locator('[class*="chart"], canvas, svg').first()
await expect(chartArea).toBeVisible({ timeout: 15000 })
})
})

View File

@@ -0,0 +1,73 @@
import { test, expect } from '@playwright/test'
test.describe('Settings Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/settings')
await page.waitForLoadState('networkidle')
})
test('displays settings page title', async ({ page }) => {
await expect(page.getByRole('heading', { name: /settings/i })).toBeVisible()
})
test('shows exchange configuration section', async ({ page }) => {
await expect(page.getByText(/exchange/i).first()).toBeVisible()
})
test('shows alert configuration section', async ({ page }) => {
await expect(page.getByText(/alert/i).first()).toBeVisible()
})
test('has tabs for different settings categories', async ({ page }) => {
// Look for tab navigation
const tabs = page.getByRole('tab')
await expect(tabs.first()).toBeVisible()
})
})
test.describe('Settings - Exchange Configuration', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/settings')
await page.waitForLoadState('networkidle')
})
test('shows add exchange option', async ({ page }) => {
// Look for add exchange button or option
const addExchange = page.getByRole('button', { name: /add.*exchange|new.*exchange|configure/i })
await expect(addExchange.first()).toBeVisible()
})
})
test.describe('Settings - Performance', () => {
test('page loads within reasonable time', async ({ page }) => {
const startTime = Date.now()
await page.goto('/settings')
await page.waitForLoadState('domcontentloaded')
const loadTime = Date.now() - startTime
// Page should load within 5 seconds
expect(loadTime).toBeLessThan(5000)
})
test('no console errors on load', async ({ page }) => {
const errors: string[] = []
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text())
}
})
await page.goto('/settings')
await page.waitForLoadState('networkidle')
// Filter out known non-critical errors
const criticalErrors = errors.filter(
(e) => !e.includes('WebSocket') && !e.includes('Failed to load resource')
)
expect(criticalErrors).toHaveLength(0)
})
})

View File

@@ -0,0 +1,79 @@
import { test, expect } from '@playwright/test'
test.describe('Strategies Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/strategies')
await page.waitForLoadState('networkidle')
})
test('displays strategies page title', async ({ page }) => {
await expect(page.getByRole('heading', { name: /strateg/i })).toBeVisible()
})
test('shows create strategy button', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add/i }).first()
await expect(createButton).toBeVisible()
})
test('displays strategy list or empty state', async ({ page }) => {
// Either show strategies or empty state message
const content = page.getByText(/no strategies|create your first|strategy/i).first()
await expect(content).toBeVisible()
})
})
test.describe('Strategies - Create Strategy Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/strategies')
await page.waitForLoadState('networkidle')
})
test('opens strategy creation dialog', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add/i }).first()
await createButton.click()
// Dialog should open
await expect(page.getByRole('dialog')).toBeVisible()
})
test('strategy dialog has required fields', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add/i }).first()
await createButton.click()
await expect(page.getByRole('dialog')).toBeVisible()
// Check for strategy name field
await expect(page.getByLabel(/name/i)).toBeVisible()
// Check for strategy type selector
await expect(page.getByLabel(/type|strategy type/i).or(page.getByText(/select.*strategy/i).first())).toBeVisible()
})
test('shows available strategy types', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add/i }).first()
await createButton.click()
await expect(page.getByRole('dialog')).toBeVisible()
// Open strategy type dropdown
const typeSelector = page.getByLabel(/type|strategy type/i).or(
page.locator('[data-testid="strategy-type-select"]')
)
await typeSelector.click()
// Should see strategy options like RSI, MACD, etc.
await expect(page.getByRole('option').first()).toBeVisible({ timeout: 5000 })
})
test('can cancel strategy creation', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add/i }).first()
await createButton.click()
await expect(page.getByRole('dialog')).toBeVisible()
// Cancel
await page.getByRole('button', { name: /cancel/i }).click()
await expect(page.getByRole('dialog')).not.toBeVisible()
})
})

View File

@@ -0,0 +1,96 @@
import { test, expect } from '@playwright/test'
test.describe('Trading Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/trading')
await page.waitForLoadState('networkidle')
})
test('displays trading page title', async ({ page }) => {
await expect(page.getByRole('heading', { name: /trading/i })).toBeVisible()
})
test('shows order form button', async ({ page }) => {
// Look for new order button or order form
const orderButton = page.getByRole('button', { name: /new order|place order|create order/i })
await expect(orderButton).toBeVisible()
})
test('displays positions section', async ({ page }) => {
// Look for positions area
await expect(page.getByText(/positions/i).first()).toBeVisible()
})
test('displays orders section', async ({ page }) => {
// Look for orders area
await expect(page.getByText(/orders/i).first()).toBeVisible()
})
test('paper trading toggle is visible', async ({ page }) => {
// Look for paper trading switch
const paperToggle = page.getByRole('switch', { name: /paper/i }).or(
page.getByText(/paper trading/i)
)
await expect(paperToggle.first()).toBeVisible()
})
})
test.describe('Trading - Order Form', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/trading')
await page.waitForLoadState('networkidle')
})
test('opens order form dialog', async ({ page }) => {
// Click new order button
const orderButton = page.getByRole('button', { name: /new order|place order|create order/i })
await orderButton.click()
// Check dialog opens
await expect(page.getByRole('dialog')).toBeVisible()
})
test('order form has required fields', async ({ page }) => {
// Open order form
const orderButton = page.getByRole('button', { name: /new order|place order|create order/i })
await orderButton.click()
await expect(page.getByRole('dialog')).toBeVisible()
// Check for symbol field
await expect(page.getByLabel(/symbol/i)).toBeVisible()
// Check for quantity field
await expect(page.getByLabel(/quantity|amount/i)).toBeVisible()
// Check for side selection (buy/sell)
await expect(page.getByText(/buy|sell/i).first()).toBeVisible()
// Check for order type
await expect(page.getByLabel(/order type|type/i)).toBeVisible()
})
test('can close order form', async ({ page }) => {
// Open order form
const orderButton = page.getByRole('button', { name: /new order|place order|create order/i })
await orderButton.click()
await expect(page.getByRole('dialog')).toBeVisible()
// Close dialog
await page.getByRole('button', { name: /cancel|close/i }).click()
await expect(page.getByRole('dialog')).not.toBeVisible()
})
})
test.describe('Trading - Balance Display', () => {
test('shows balance information', async ({ page }) => {
await page.goto('/trading')
await page.waitForLoadState('networkidle')
// Look for balance display
const balanceText = page.getByText(/balance|total|available/i)
await expect(balanceText.first()).toBeVisible()
})
})

13
frontend/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FXQ One</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6715
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

51
frontend/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "fxq-one-frontend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@tanstack/react-query": "^5.12.0",
"axios": "^1.6.2",
"date-fns": "^3.0.0",
"lightweight-charts": "^4.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0",
"recharts": "^2.10.3",
"zustand": "^4.4.7"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^4.0.16",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"jsdom": "^27.3.0",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vitest": "^4.0.16"
}
}

View File

@@ -0,0 +1,56 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Playwright E2E Test Configuration
* See https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use */
reporter: 'html',
/* Shared settings for all the projects below */
use: {
/* Base URL to use in actions like `await page.goto('/')` */
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test */
trace: 'on-first-retry',
/* Take screenshot on failure */
screenshot: 'only-on-failure',
/* Record video on failure */
video: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
/* Uncomment for WebKit testing
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
*/
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
})

BIN
frontend/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

1
frontend/public/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.8 MiB

71
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,71 @@
import { Routes, Route } from 'react-router-dom'
import { Box, Alert } from '@mui/material'
import { Component, ReactNode } from 'react'
import Layout from './components/Layout'
import DashboardPage from './pages/DashboardPage'
import PortfolioPage from './pages/PortfolioPage'
import BacktestPage from './pages/BacktestPage'
import SettingsPage from './pages/SettingsPage'
import StrategiesPage from './pages/StrategiesPage'
import TradingPage from './pages/TradingPage'
import { useRealtimeData } from './hooks/useRealtimeData'
// Error Boundary Component
class ErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean; error: Error | null }
> {
constructor(props: { children: ReactNode }) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<Box sx={{ p: 3 }}>
<Alert severity="error">
<strong>Something went wrong:</strong> {this.state.error?.message || 'Unknown error'}
</Alert>
<Box sx={{ mt: 2 }}>
<pre style={{ fontSize: '12px', overflow: 'auto' }}>
{this.state.error?.stack}
</pre>
</Box>
</Box>
)
}
return this.props.children
}
}
function App() {
// Enable real-time data updates
useRealtimeData()
return (
<ErrorBoundary>
<Layout>
<Routes>
<Route path="/" element={<DashboardPage />} />
<Route path="/strategies" element={<StrategiesPage />} />
<Route path="/trading" element={<TradingPage />} />
<Route path="/portfolio" element={<PortfolioPage />} />
<Route path="/backtesting" element={<BacktestPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
</Layout>
</ErrorBoundary>
)
}
export default App

View File

@@ -0,0 +1,5 @@
export { apiClient } from './client'
export * as tradingApi from './trading'
export * as portfolioApi from './portfolio'
export * as strategiesApi from './strategies'
export * as backtestingApi from './backtesting'

View File

@@ -0,0 +1,105 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { autopilotApi } from '../autopilot'
import { apiClient } from '../client'
vi.mock('../client')
describe('autopilotApi', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('getModes', () => {
it('calls correct endpoint', async () => {
const mockResponse = {
data: {
modes: {
pattern: { name: 'Pattern-Based' },
intelligent: { name: 'ML-Based' },
},
},
}
vi.mocked(apiClient.get).mockResolvedValue(mockResponse)
const result = await autopilotApi.getModes()
expect(apiClient.get).toHaveBeenCalledWith('/api/autopilot/modes')
expect(result).toEqual(mockResponse.data)
})
})
describe('startUnified', () => {
it('calls correct endpoint with config', async () => {
const config = {
symbol: 'BTC/USD',
mode: 'pattern' as const,
auto_execute: false,
}
const mockResponse = { data: { status: 'started' } }
vi.mocked(apiClient.post).mockResolvedValue(mockResponse)
const result = await autopilotApi.startUnified(config)
expect(apiClient.post).toHaveBeenCalledWith('/api/autopilot/start-unified', config)
expect(result).toEqual(mockResponse.data)
})
})
describe('stopUnified', () => {
it('calls correct endpoint with parameters', async () => {
const mockResponse = { data: { status: 'stopped' } }
vi.mocked(apiClient.post).mockResolvedValue(mockResponse)
const result = await autopilotApi.stopUnified('BTC/USD', 'pattern', '1h')
expect(apiClient.post).toHaveBeenCalledWith(
'/api/autopilot/stop-unified?symbol=BTC/USD&mode=pattern&timeframe=1h'
)
expect(result).toEqual(mockResponse.data)
})
})
describe('getUnifiedStatus', () => {
it('calls correct endpoint with parameters', async () => {
const mockResponse = {
data: {
running: true,
mode: 'pattern',
},
}
vi.mocked(apiClient.get).mockResolvedValue(mockResponse)
const result = await autopilotApi.getUnifiedStatus('BTC/USD', 'pattern', '1h')
expect(apiClient.get).toHaveBeenCalledWith(
'/api/autopilot/status-unified/BTC/USD?mode=pattern&timeframe=1h'
)
expect(result).toEqual(mockResponse.data)
})
})
describe('backward compatibility', () => {
it('start method still exists', () => {
expect(autopilotApi.start).toBeDefined()
})
it('stop method still exists', () => {
expect(autopilotApi.stop).toBeDefined()
})
it('getStatus method still exists', () => {
expect(autopilotApi.getStatus).toBeDefined()
})
it('startIntelligent method still exists', () => {
expect(autopilotApi.startIntelligent).toBeDefined()
})
it('stopIntelligent method still exists', () => {
expect(autopilotApi.stopIntelligent).toBeDefined()
})
})
})

Some files were not shown because too many files have changed in this diff Show More