├── src ├── __init__.py ├── pic │ └── logo.png ├── utils │ ├── __init__.py │ ├── logger.py │ ├── timezone_utils.py │ ├── validators.py │ ├── error_handling.py │ └── watchlist_manager.py ├── trade │ ├── __init__.py │ ├── portfolio.py │ └── trading_engine.py ├── analysis │ ├── __init__.py │ ├── performance_analyzer.py │ └── technical_analysis.py ├── ui │ ├── __init__.py │ └── simple_cli.py ├── option_strategy │ └── __init__.py ├── data │ ├── __init__.py │ ├── market_info_provider.py │ ├── news_provider.py │ ├── macro_provider.py │ ├── fred_provider.py │ └── finnhub_provider.py ├── account │ ├── __init__.py │ └── account_config.py ├── agent │ ├── __init__.py │ ├── agent_manager.py │ └── llm_providers.py └── stock_strategy │ ├── __init__.py │ └── base_strategy.py ├── config ├── __init__.py └── wheel_config.json ├── watchlist.json ├── .gitignore ├── examples ├── run_backtest_example.py ├── README.md ├── wheel_strategy_example.py └── simple_example.py ├── .env.example ├── requirements.txt ├── main.py ├── README_WHEEL.md ├── pyproject.toml ├── tests └── test_dashboard.py ├── dashboard.py ├── wheel_cli.py └── docs └── PROJECT_STRUCTURE.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pic/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Magica-Chen/GaussWorldTrader/HEAD/src/pic/logo.png -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import Config, get_config, reload_config, OptimizedConfig 2 | 3 | __all__ = ['Config', 'get_config', 'reload_config', 'OptimizedConfig'] -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import setup_logger 2 | from .validators import validate_symbol, validate_timeframe 3 | 4 | __all__ = ['setup_logger', 'validate_symbol', 'validate_timeframe'] -------------------------------------------------------------------------------- /src/trade/__init__.py: -------------------------------------------------------------------------------- 1 | from .trading_engine import TradingEngine 2 | from .portfolio import Portfolio 3 | from .backtester import Backtester 4 | 5 | __all__ = ['TradingEngine', 'Portfolio', 'Backtester'] -------------------------------------------------------------------------------- /src/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from .technical_analysis import TechnicalAnalysis 2 | from .financial_metrics import FinancialMetrics 3 | from .performance_analyzer import PerformanceAnalyzer 4 | 5 | __all__ = ['TechnicalAnalysis', 'FinancialMetrics', 'PerformanceAnalyzer'] -------------------------------------------------------------------------------- /src/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # CLI interfaces using core abstraction 2 | # - core_cli.py: Base functionality and shared utilities 3 | # - modern_cli.py: Advanced CLI with async operations (primary) 4 | # - simple_cli.py: Basic CLI interface (fallback) 5 | # Use main.py as the primary entry point 6 | 7 | __all__ = ["core_cli", "modern_cli", "simple_cli"] -------------------------------------------------------------------------------- /src/option_strategy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Option Trading Strategies Module 3 | 4 | This module contains implementation of various option trading strategies 5 | for the Gauss World Trader system. 6 | 7 | Available Strategies: 8 | - WheelStrategy: The wheel options strategy implementation 9 | """ 10 | 11 | from .wheel_strategy import WheelStrategy 12 | 13 | __all__ = ['WheelStrategy'] 14 | -------------------------------------------------------------------------------- /watchlist.json: -------------------------------------------------------------------------------- 1 | { 2 | "watchlist": [ 3 | "RGTI", 4 | "AFRM", 5 | "UPST", 6 | "OPFI", 7 | "PGY", 8 | "ETOR", 9 | "MARA", 10 | "CRCL", 11 | "QUBT", 12 | "GME", 13 | "SMCI", 14 | "BMNR", 15 | "DFDV", 16 | "CNC" 17 | ], 18 | "metadata": { 19 | "created": "2025-08-21", 20 | "last_updated": "2025-08-28 22:44:14", 21 | "description": "Gauss World Trader Default Watchlist", 22 | "version": "1.0" 23 | } 24 | } -------------------------------------------------------------------------------- /src/data/__init__.py: -------------------------------------------------------------------------------- 1 | from .alpaca_provider import AlpacaDataProvider 2 | from .news_provider import NewsDataProvider 3 | from .macro_provider import MacroDataProvider 4 | from .finnhub_provider import FinnhubProvider 5 | from .fred_provider import FREDProvider 6 | from .market_info_provider import get_comprehensive_market_data 7 | 8 | __all__ = [ 9 | 'AlpacaDataProvider', 10 | 'NewsDataProvider', 11 | 'MacroDataProvider', 12 | 'FinnhubProvider', 13 | 'FREDProvider', 14 | 'get_comprehensive_market_data' 15 | ] -------------------------------------------------------------------------------- /src/account/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Account Management Module for Gauss World Trader 3 | 4 | Comprehensive Alpaca account management including configurations, 5 | positions, orders, and portfolio information. 6 | """ 7 | 8 | from .account_manager import AccountManager 9 | from .position_manager import PositionManager 10 | from .order_manager import OrderManager 11 | from .portfolio_tracker import PortfolioTracker 12 | from .account_config import AccountConfigurator 13 | 14 | __all__ = [ 15 | 'AccountManager', 'PositionManager', 'OrderManager', 16 | 'PortfolioTracker', 'AccountConfigurator' 17 | ] -------------------------------------------------------------------------------- /config/wheel_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_risk": 80000, 3 | "position_size_pct": 0.08, 4 | "put_delta_min": 0.15, 5 | "put_delta_max": 0.3, 6 | "call_delta_min": 0.15, 7 | "call_delta_max": 0.3, 8 | "min_yield": 0.04, 9 | "max_yield": 1.0, 10 | "dte_min": 7, 11 | "dte_max": 45, 12 | "preferred_dte": 21, 13 | "min_open_interest": 100, 14 | "min_daily_volume": 50, 15 | "min_score": 0.05, 16 | "max_options_per_underlying": 1, 17 | "assignment_tolerance": 0.8, 18 | "profit_target": 0.5, 19 | "management_dte": 7, 20 | "max_positions": 10, 21 | "min_stock_price": 10.0, 22 | "max_stock_price": 500.0 23 | } -------------------------------------------------------------------------------- /src/agent/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AI Agent Module for Gauss World Trader 3 | 4 | This module provides intelligent analysis capabilities using multiple LLM providers 5 | and financial data sources including Finnhub and FRED APIs. 6 | """ 7 | 8 | from .llm_providers import OpenAIProvider, DeepSeekProvider, ClaudeProvider, MoonshotProvider 9 | from src.data.finnhub_provider import FinnhubProvider 10 | from src.data.fred_provider import FREDProvider 11 | from .fundamental_analyzer import FundamentalAnalyzer 12 | from .agent_manager import AgentManager 13 | 14 | __all__ = [ 15 | 'OpenAIProvider', 'DeepSeekProvider', 'ClaudeProvider', 'MoonshotProvider', 16 | 'FinnhubProvider', 'FREDProvider', 'FundamentalAnalyzer', 'AgentManager' 17 | ] -------------------------------------------------------------------------------- /src/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from datetime import datetime 4 | import os 5 | 6 | def setup_logger(name: str = 'trading_system', level: str = 'INFO', 7 | log_file: str = None) -> logging.Logger: 8 | 9 | logger = logging.getLogger(name) 10 | 11 | # Clear existing handlers 12 | for handler in logger.handlers[:]: 13 | logger.removeHandler(handler) 14 | 15 | # Set level 16 | logger.setLevel(getattr(logging, level.upper())) 17 | 18 | # Create formatter 19 | formatter = logging.Formatter( 20 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s', 21 | datefmt='%Y-%m-%d %H:%M:%S' 22 | ) 23 | 24 | # Console handler 25 | console_handler = logging.StreamHandler(sys.stdout) 26 | console_handler.setFormatter(formatter) 27 | logger.addHandler(console_handler) 28 | 29 | # File handler (optional) 30 | if log_file: 31 | os.makedirs(os.path.dirname(log_file), exist_ok=True) 32 | file_handler = logging.FileHandler(log_file) 33 | file_handler.setFormatter(formatter) 34 | logger.addHandler(file_handler) 35 | 36 | return logger -------------------------------------------------------------------------------- /src/stock_strategy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trading Strategy Framework 3 | 4 | This module contains all trading strategy implementations 5 | Includes classical and modern ML-based strategies 6 | """ 7 | 8 | from .base_strategy import BaseStrategy 9 | from .momentum_strategy import MomentumStrategy 10 | 11 | # Classical High-Frequency Strategies 12 | from .scalping_strategy import ScalpingStrategy 13 | from .arbitrage_strategy import StatisticalArbitrageStrategy 14 | 15 | # Classical Low-Frequency Strategies 16 | from .trend_following_strategy import TrendFollowingStrategy 17 | from .value_strategy import ValueInvestmentStrategy 18 | 19 | # Modern ML-Based Strategies 20 | from .xgboost_strategy import XGBoostStrategy 21 | from .deep_learning_strategy import DeepLearningStrategy 22 | from .gaussian_process_strategy import GaussianProcessStrategy 23 | 24 | __all__ = [ 25 | 'BaseStrategy', 26 | 'MomentumStrategy', 27 | # High Frequency 28 | 'ScalpingStrategy', 29 | 'StatisticalArbitrageStrategy', 30 | # Low Frequency 31 | 'TrendFollowingStrategy', 32 | 'ValueInvestmentStrategy', 33 | # Machine Learning 34 | 'XGBoostStrategy', 35 | 'DeepLearningStrategy', 36 | 'GaussianProcessStrategy' 37 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | .env.local 4 | .env.development.local 5 | .env.test.local 6 | .env.production.local 7 | 8 | # Python 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | *.so 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # Virtual environments 33 | venv/ 34 | env/ 35 | ENV/ 36 | env.bak/ 37 | venv.bak/ 38 | 39 | # IDE 40 | .vscode/ 41 | .idea/ 42 | *.swp 43 | *.swo 44 | *~ 45 | 46 | # Jupyter Notebook 47 | .ipynb_checkpoints 48 | 49 | # PyCharm 50 | .idea/ 51 | 52 | # Database 53 | *.db 54 | *.sqlite 55 | *.sqlite3 56 | 57 | # Logs 58 | *.log 59 | logs/ 60 | 61 | # Trading data and results 62 | results/ 63 | archive/ 64 | backtest_results/ 65 | 66 | # OS generated files 67 | .DS_Store 68 | .DS_Store? 69 | ._* 70 | .Spotlight-V100 71 | .Trashes 72 | ehthumbs.db 73 | Thumbs.db 74 | 75 | # Temporary files 76 | *.tmp 77 | temp/ 78 | 79 | # Coverage reports 80 | htmlcov/ 81 | .coverage 82 | .coverage.* 83 | coverage.xml 84 | *.cover 85 | .hypothesis/ 86 | .pytest_cache/ 87 | 88 | # MyPy 89 | .mypy_cache/ 90 | .dmypy.json 91 | dmypy.json 92 | 93 | #Claude 94 | CLAUDE.md -------------------------------------------------------------------------------- /examples/run_backtest_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Gauss World Trader - Enhanced Backtest Runner with CSV Export 4 | Runs momentum strategy backtest and generates transaction log 5 | Named after Carl Friedrich Gauss, pioneer of mathematical finance 6 | """ 7 | 8 | import sys 9 | import os 10 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 11 | 12 | from examples.momentum_backtest_example import momentum_backtest_example 13 | 14 | def main(): 15 | print("🌍 Gauss World Trader - Enhanced Momentum Strategy Backtest") 16 | print("=" * 70) 17 | print("📊 Features:") 18 | print(" ✅ Full backtest with performance metrics") 19 | print(" ✅ P&L plot generation and display") 20 | print(" ✅ Detailed transaction CSV export") 21 | print(" ✅ Trading summary and position analysis") 22 | print("=" * 70) 23 | 24 | try: 25 | momentum_backtest_example() 26 | except KeyboardInterrupt: 27 | print("\\n⏹️ Backtest interrupted by user") 28 | except Exception as e: 29 | print(f"\\n❌ Error running backtest: {e}") 30 | print("\\nTroubleshooting:") 31 | print("1. Check your .env file has valid Alpaca API keys") 32 | print("2. Ensure all packages are installed: pip install matplotlib pandas") 33 | print("3. Make sure you're in the correct directory") 34 | 35 | if __name__ == '__main__': 36 | main() -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Gauss World Trader - Environment Configuration 2 | # Repository: https://github.com/Magica-Chen/GaussWorldTrader 3 | 4 | # Alpaca Trading API (Paper Trading) 5 | ALPACA_API_KEY=your_alpaca_key 6 | ALPACA_SECRET_KEY=your_alpaca_secret 7 | ALPACA_BASE_URL=https://paper-api.alpaca.markets 8 | 9 | # Financial Data APIs 10 | FINNHUB_API_KEY=your_finnhub_key 11 | FRED_API_KEY=your_fred_key 12 | 13 | # LLM API Keys and Configuration 14 | # OpenAI Configuration 15 | OPENAI_API_KEY=your_openai_api_key_here 16 | OPENAI_BASE_URL=https://api.openai.com/v1 17 | OPENAI_MODEL=gpt-4-turbo-preview 18 | 19 | # Moonshot AI Configuration 20 | MOONSHOT_API_KEY=your_moonshot_api_key_here 21 | MOONSHOT_BASE_URL=https://api.moonshot.cn/v1 22 | MOONSHOT_MODEL=moonshot-v1-8k 23 | 24 | # DeepSeek Configuration 25 | DEEPSEEK_API_KEY=your_deepseek_api_key_here 26 | DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 27 | DEEPSEEK_MODEL=deepseek-chat 28 | 29 | # Anthropic (Claude) Configuration 30 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 31 | ANTHROPIC_BASE_URL=https://api.anthropic.com 32 | ANTHROPIC_MODEL=claude-3-sonnet-20240229 33 | 34 | # Google Gemini Configuration 35 | GEMINI_API_KEY=your_gemini_api_key_here 36 | GEMINI_MODEL=gemini-pro 37 | 38 | # Default LLM Provider (options: openai, moonshot, deepseek, anthropic, claude, gemini) 39 | DEFAULT_LLM_PROVIDER=openai 40 | 41 | # Database Configuration (Optional) 42 | DATABASE_URL=sqlite:///gauss_world_trader.db 43 | 44 | # Logging Configuration 45 | LOG_LEVEL=INFO -------------------------------------------------------------------------------- /src/data/market_info_provider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Comprehensive market data aggregator 3 | """ 4 | 5 | from typing import Dict, Any 6 | from datetime import datetime, timedelta 7 | 8 | from .finnhub_provider import FinnhubProvider 9 | from .fred_provider import FREDProvider 10 | 11 | 12 | def get_comprehensive_market_data(symbol: str, 13 | finnhub_key: str = None, 14 | fred_key: str = None) -> Dict[str, Any]: 15 | """Get comprehensive market data from multiple sources""" 16 | data = {} 17 | 18 | # Finnhub data 19 | finnhub = FinnhubProvider(finnhub_key) 20 | data['company_profile'] = finnhub.get_company_profile(symbol) 21 | data['basic_financials'] = finnhub.get_basic_financials(symbol) 22 | data['company_news'] = finnhub.get_company_news(symbol) 23 | data['recommendations'] = finnhub.get_recommendation_trends(symbol) 24 | data['price_target'] = finnhub.get_price_target(symbol) 25 | data['quote'] = finnhub.get_quote(symbol) 26 | data['earnings_surprises'] = finnhub.get_earnings_surprises(symbol) 27 | data['insider_transactions'] = finnhub.get_insider_transactions(symbol) 28 | data['insider_sentiment'] = finnhub.get_insider_sentiment(symbol) 29 | 30 | # FRED economic data 31 | fred = FREDProvider(fred_key) 32 | start_date = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d') 33 | data['economic_indicators'] = fred.get_economic_indicators(start_date) 34 | 35 | return data -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples Directory 2 | 3 | This directory contains example scripts demonstrating various features of Gauss World Trader. 4 | 5 | ## Available Examples 6 | 7 | ### 1. `simple_example.py` 8 | Basic usage example showing: 9 | - Simple trading strategy implementation 10 | - Market data fetching 11 | - Basic backtesting 12 | 13 | ### 2. `momentum_backtest_example.py` 14 | Momentum strategy backtesting example showing: 15 | - Advanced backtesting with performance analytics 16 | - Chart generation and visualization 17 | - CSV transaction export 18 | 19 | ### 3. `advanced_strategies_example.py` 20 | Comprehensive strategy demonstration showing: 21 | - All available trading strategies 22 | - Strategy comparison and selection 23 | - Performance metrics analysis 24 | 25 | ### 4. `run_backtest_example.py` 26 | Command-line backtesting example showing: 27 | - CLI-based backtesting 28 | - Multiple symbol analysis 29 | - Results visualization 30 | 31 | ## Running Examples 32 | 33 | From the project root directory: 34 | 35 | ```bash 36 | # Simple trading example 37 | python examples/simple_example.py 38 | 39 | # Momentum strategy backtest 40 | python examples/momentum_backtest_example.py 41 | 42 | # Advanced strategies demonstration 43 | python examples/advanced_strategies_example.py 44 | 45 | # CLI backtest example 46 | python examples/run_backtest_example.py 47 | ``` 48 | 49 | ## Requirements 50 | 51 | All examples require the same dependencies as the main application. Ensure you have: 52 | - Python 3.12+ 53 | - All packages from `requirements.txt` 54 | - Valid API credentials in `.env` file 55 | 56 | ## Example Data 57 | 58 | Examples use real market data when API credentials are available, or demonstrate with sample data otherwise. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Optimized for Python 3.12+ 2 | # Core Dependencies 3 | pandas>=2.1.0 4 | numpy>=1.26.0 5 | requests>=2.31.0 6 | python-dotenv>=1.0.0 7 | 8 | # Trading API 9 | alpaca-py>=0.25.0 10 | 11 | # Technical Analysis 12 | ta-lib>=0.4.28 13 | 14 | # Data Analysis and Visualization 15 | matplotlib>=3.8.0 16 | plotly>=5.17.0 17 | seaborn>=0.13.0 18 | 19 | # Web Interface 20 | streamlit>=1.28.0 21 | dash>=2.14.0 22 | dash-bootstrap-components>=1.5.0 23 | 24 | # Financial Data 25 | yfinance>=0.2.20 26 | fredapi>=0.5.1 27 | finnhub-python>=2.4.20 28 | 29 | # Statistical Analysis 30 | scipy>=1.11.0 31 | scikit-learn>=1.3.0 32 | statsmodels>=0.14.0 33 | 34 | # Database 35 | sqlalchemy>=2.0.0 36 | 37 | # Testing 38 | pytest>=7.4.0 39 | pytest-cov>=4.1.0 40 | pytest-asyncio>=0.21.0 41 | 42 | # Logging and Configuration 43 | loguru>=0.7.0 44 | 45 | # CLI Interface 46 | click>=8.1.0 47 | rich>=13.6.0 48 | typer>=0.9.0 49 | 50 | # Async Support (optimized for Python 3.12) 51 | aiohttp>=3.9.0 52 | websockets>=9.0 53 | aiofiles>=23.0.0 54 | asyncpg>=0.29.0 55 | 56 | # Performance (Python 3.12 optimized) 57 | numba>=0.58.0 58 | cython>=3.0.0 59 | 60 | # Type Checking (Python 3.12 features) 61 | mypy>=1.6.0 62 | typing-extensions>=4.8.0 63 | 64 | # Development Tools (Python 3.12 compatible) 65 | black>=23.9.0 66 | ruff>=0.1.0 67 | pre-commit>=3.5.0 68 | 69 | # Documentation 70 | sphinx>=7.2.0 71 | sphinx-rtd-theme>=1.3.0 72 | 73 | # Time and timezone handling 74 | pytz>=2023.3 75 | 76 | # Dashboard enhancements (optional) 77 | streamlit-option-menu>=0.3.6 78 | 79 | # Additional Python 3.12 optimizations 80 | tomli>=2.0.1; python_version < "3.11" 81 | tomli-w>=1.0.0 82 | pydantic>=2.4.0 83 | httpx>=0.25.0 84 | 85 | # Optional ML dependencies (install separately if needed) 86 | # tensorflow>=2.13.0 # For deep learning strategies 87 | # xgboost>=1.7.0 # For XGBoost strategies -------------------------------------------------------------------------------- /src/utils/timezone_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Timezone utilities for trading operations 3 | Centralizes timezone handling for the entire application 4 | """ 5 | import pytz 6 | from datetime import datetime, time 7 | 8 | # Global timezone for all trading operations 9 | EASTERN = pytz.timezone('US/Eastern') 10 | 11 | def get_market_hours(): 12 | """Get standard market hours in ET""" 13 | return { 14 | 'market_open': time(9, 30), # 9:30 AM ET 15 | 'market_close': time(16, 0), # 4:00 PM ET 16 | 'premarket_start': time(4, 0), # 4:00 AM ET 17 | 'postmarket_end': time(20, 0) # 8:00 PM ET 18 | } 19 | 20 | def get_market_status(current_time: datetime = None) -> str: 21 | """ 22 | Determine current market status 23 | Returns: 'closed', 'open', 'pre-market', 'post-market' 24 | """ 25 | if current_time is None: 26 | current_time = datetime.now(EASTERN) 27 | elif current_time.tzinfo is None: 28 | current_time = EASTERN.localize(current_time) 29 | else: 30 | current_time = current_time.astimezone(EASTERN) 31 | 32 | # Check if it's a weekend 33 | if current_time.weekday() >= 5: # Saturday = 5, Sunday = 6 34 | return 'closed' 35 | 36 | current_time_only = current_time.time() 37 | market_hours = get_market_hours() 38 | 39 | if current_time_only < market_hours['premarket_start']: 40 | return 'closed' 41 | elif current_time_only < market_hours['market_open']: 42 | return 'pre-market' 43 | elif current_time_only <= market_hours['market_close']: 44 | return 'open' 45 | elif current_time_only <= market_hours['postmarket_end']: 46 | return 'post-market' 47 | else: 48 | return 'closed' 49 | 50 | def now_et() -> datetime: 51 | """Get current time in Eastern timezone""" 52 | return datetime.now(EASTERN) 53 | 54 | def to_et(dt: datetime) -> datetime: 55 | """Convert datetime to Eastern timezone""" 56 | if dt.tzinfo is None: 57 | return EASTERN.localize(dt) 58 | else: 59 | return dt.astimezone(EASTERN) -------------------------------------------------------------------------------- /src/utils/validators.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List, Optional 3 | from datetime import datetime 4 | 5 | def validate_symbol(symbol: str) -> bool: 6 | if not isinstance(symbol, str): 7 | return False 8 | 9 | # Basic stock symbol validation (1-5 uppercase letters) 10 | pattern = r'^[A-Z]{1,5}$' 11 | return bool(re.match(pattern, symbol.upper())) 12 | 13 | def validate_timeframe(timeframe: str) -> bool: 14 | valid_timeframes = [ 15 | '1Min', '5Min', '15Min', '30Min', '1Hour', '1Day', '1Week', '1Month' 16 | ] 17 | return timeframe in valid_timeframes 18 | 19 | def validate_date_range(start_date: datetime, end_date: datetime) -> bool: 20 | if not isinstance(start_date, datetime) or not isinstance(end_date, datetime): 21 | return False 22 | 23 | return start_date < end_date and end_date <= datetime.now() 24 | 25 | def validate_portfolio_weights(weights: List[float]) -> bool: 26 | if not weights or not all(isinstance(w, (int, float)) for w in weights): 27 | return False 28 | 29 | return abs(sum(weights) - 1.0) < 1e-6 and all(w >= 0 for w in weights) 30 | 31 | def validate_price(price: float) -> bool: 32 | return isinstance(price, (int, float)) and price > 0 33 | 34 | def validate_quantity(quantity: int) -> bool: 35 | return isinstance(quantity, int) and quantity > 0 36 | 37 | def sanitize_symbol(symbol: str) -> Optional[str]: 38 | if not isinstance(symbol, str): 39 | return None 40 | 41 | # Remove whitespace and convert to uppercase 42 | clean_symbol = symbol.strip().upper() 43 | 44 | # Validate the cleaned symbol 45 | if validate_symbol(clean_symbol): 46 | return clean_symbol 47 | 48 | return None 49 | 50 | def validate_api_key(api_key: str, min_length: int = 20) -> bool: 51 | if not isinstance(api_key, str): 52 | return False 53 | 54 | return len(api_key.strip()) >= min_length 55 | 56 | def validate_percentage(value: float, min_val: float = 0.0, max_val: float = 100.0) -> bool: 57 | return isinstance(value, (int, float)) and min_val <= value <= max_val 58 | 59 | def convert_crypto_symbol_for_display(symbol: str) -> str: 60 | """ 61 | Convert crypto symbols to consistent display format. 62 | Converts BTCUSD (position format) to BTC/USD (display/API format). 63 | """ 64 | if not isinstance(symbol, str): 65 | return symbol 66 | 67 | # Known crypto symbol mappings (position format -> display format) 68 | crypto_mappings = { 69 | 'BTCUSD': 'BTC/USD', 70 | 'ETHUSD': 'ETH/USD', 71 | 'LTCUSD': 'LTC/USD', 72 | 'BCHUSD': 'BCH/USD', 73 | 'ADAUSD': 'ADA/USD', 74 | 'DOTUSD': 'DOT/USD', 75 | 'UNIUSD': 'UNI/USD', 76 | 'LINKUSD': 'LINK/USD', 77 | 'XLMUSD': 'XLM/USD', 78 | 'ALGOUSD': 'ALGO/USD' 79 | } 80 | 81 | # Convert if it's a known crypto symbol, otherwise return as-is 82 | return crypto_mappings.get(symbol.upper(), symbol) -------------------------------------------------------------------------------- /src/data/news_provider.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from typing import List, Dict, Any, Optional 4 | from datetime import datetime, timedelta 5 | 6 | from .finnhub_provider import FinnhubProvider 7 | 8 | 9 | class NewsDataProvider: 10 | def __init__(self, api_key: str = None): 11 | self.finnhub = FinnhubProvider(api_key) 12 | self.logger = logging.getLogger(__name__) 13 | 14 | def get_company_news(self, symbol: str, from_date: Optional[datetime] = None, 15 | to_date: Optional[datetime] = None) -> List[Dict[str, Any]]: 16 | if from_date is None: 17 | from_date = datetime.now() - timedelta(days=30) 18 | if to_date is None: 19 | to_date = datetime.now() 20 | 21 | from_str = from_date.strftime('%Y-%m-%d') 22 | to_str = to_date.strftime('%Y-%m-%d') 23 | 24 | result = self.finnhub.get_company_news(symbol, from_str, to_str) 25 | 26 | if isinstance(result, list) and len(result) > 0 and "error" in result[0]: 27 | self.logger.error(f"Error fetching company news: {result[0]['error']}") 28 | return [] 29 | 30 | return result if isinstance(result, list) else [] 31 | 32 | def get_market_news(self, category: str = "general") -> List[Dict[str, Any]]: 33 | result = self.finnhub.get_market_news(category) 34 | 35 | if isinstance(result, list) and len(result) > 0 and "error" in result[0]: 36 | self.logger.error(f"Error fetching market news: {result[0]['error']}") 37 | return [] 38 | 39 | return result if isinstance(result, list) else [] 40 | 41 | def get_insider_transactions(self, symbol: str) -> List[Dict[str, Any]]: 42 | """Get insider transactions""" 43 | result = self.finnhub.get_insider_transactions(symbol) 44 | 45 | if isinstance(result, list) and len(result) > 0 and "error" in result[0]: 46 | self.logger.error(f"Error fetching insider transactions: {result[0]['error']}") 47 | return [] 48 | 49 | return result if isinstance(result, list) else [] 50 | 51 | def get_insider_sentiment(self, symbol: str, 52 | from_date: Optional[datetime] = None, 53 | to_date: Optional[datetime] = None) -> Dict[str, Any]: 54 | """Get insider sentiment""" 55 | if from_date is None: 56 | from_date = datetime.now() - timedelta(days=90) 57 | if to_date is None: 58 | to_date = datetime.now() 59 | 60 | from_str = from_date.strftime('%Y-%m-%d') 61 | to_str = to_date.strftime('%Y-%m-%d') 62 | 63 | result = self.finnhub.get_insider_sentiment(symbol, from_str, to_str) 64 | 65 | if "error" in result: 66 | self.logger.error(f"Error fetching insider sentiment: {result['error']}") 67 | return {} 68 | 69 | return result 70 | 71 | def search_news(self, query: str, from_date: Optional[datetime] = None, 72 | to_date: Optional[datetime] = None) -> List[Dict[str, Any]]: 73 | if from_date is None: 74 | from_date = datetime.now() - timedelta(days=7) 75 | if to_date is None: 76 | to_date = datetime.now() 77 | 78 | try: 79 | news_data = self.get_market_news("general") 80 | 81 | filtered_news = [] 82 | for article in news_data: 83 | if (query.lower() in article.get('headline', '').lower() or 84 | query.lower() in article.get('summary', '').lower()): 85 | 86 | article_date = datetime.fromtimestamp(article.get('datetime', 0)) 87 | if from_date <= article_date <= to_date: 88 | filtered_news.append(article) 89 | 90 | return filtered_news 91 | except Exception as e: 92 | print(f"Error searching news: {e}") 93 | return [] -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Gauss World Trader - Python 3.12+ Optimized Entry Point 4 | Features async operations, rich CLI, and performance monitoring 5 | Named after Carl Friedrich Gauss, pioneer of mathematical finance 6 | """ 7 | import sys 8 | import argparse 9 | from pathlib import Path 10 | 11 | # Add the project root to the Python path 12 | project_root = Path(__file__).parent 13 | sys.path.insert(0, str(project_root)) 14 | 15 | # Check Python version 16 | if sys.version_info < (3, 12): 17 | print("🚨 Gauss World Trader is optimized for Python 3.12+") 18 | print(f"📍 Current version: Python {sys.version_info.major}.{sys.version_info.minor}") 19 | print("💡 Please upgrade to Python 3.12 for optimal performance") 20 | sys.exit(1) 21 | 22 | from config.config import get_config 23 | 24 | # Import new modules 25 | try: 26 | from src.account import AccountManager, PositionManager, OrderManager 27 | from src.agent import AgentManager 28 | ACCOUNT_MODULE_AVAILABLE = True 29 | AGENT_MODULE_AVAILABLE = True 30 | except ImportError as e: 31 | print(f"Warning: Some modules not available: {e}") 32 | ACCOUNT_MODULE_AVAILABLE = False 33 | AGENT_MODULE_AVAILABLE = False 34 | 35 | try: 36 | from rich.console import Console 37 | console = Console() 38 | HAS_RICH = True 39 | except ImportError: 40 | HAS_RICH = False 41 | console = None 42 | 43 | 44 | def parse_cli_arguments(): 45 | """Parse command line arguments for CLI selection.""" 46 | parser = argparse.ArgumentParser( 47 | description="🌍 Gauss World Trader - Quantitative Trading System", 48 | add_help=False # We'll handle help through the selected CLI 49 | ) 50 | 51 | parser.add_argument( 52 | '--cli', 53 | choices=['modern', 'simple'], 54 | default='modern', 55 | help='Choose CLI interface: modern (default) or simple' 56 | ) 57 | 58 | # Parse only known args to allow CLI-specific arguments to pass through 59 | args, remaining = parser.parse_known_args() 60 | return args.cli, remaining 61 | 62 | 63 | def show_startup_banner(cli_type: str): 64 | """Show startup banner with CLI type information.""" 65 | cli_description = "Modern Rich CLI" if cli_type == "modern" else "Simple CLI" 66 | 67 | if HAS_RICH and console: 68 | console.print(f""" 69 | [bold blue]🌍 Gauss World Trader[/bold blue] 70 | [cyan]Python 3.12 Compatible • High Performance Trading • Named after Carl Friedrich Gauss[/cyan] 71 | [dim]Interface: {cli_description}[/dim] 72 | """) 73 | 74 | # Basic config validation 75 | try: 76 | config = get_config() 77 | console.print(config.get_validation_summary()) 78 | 79 | except Exception as e: 80 | console.print(f"[red]❌ Configuration Error: {e}[/red]") 81 | console.print("[yellow]💡 Please check your .env file and API credentials[/yellow]") 82 | sys.exit(1) 83 | else: 84 | print("🌍 Gauss World Trader") 85 | print("Python 3.12 Compatible • High Performance Trading") 86 | print("Named after Carl Friedrich Gauss") 87 | print(f"Interface: {cli_description}") 88 | print("=" * 50) 89 | 90 | 91 | def main() -> None: 92 | """ 93 | Main entry point with CLI selection 94 | - Parses CLI selection arguments 95 | - Validates configuration 96 | - Shows system status 97 | - Launches selected CLI interface 98 | """ 99 | 100 | # Parse CLI selection arguments 101 | cli_type, remaining_args = parse_cli_arguments() 102 | 103 | # Show startup banner with selected CLI info 104 | show_startup_banner(cli_type) 105 | 106 | # Temporarily modify sys.argv to pass remaining arguments to the selected CLI 107 | original_argv = sys.argv 108 | sys.argv = [sys.argv[0]] + remaining_args 109 | 110 | try: 111 | if cli_type == 'simple': 112 | # Import and launch simple CLI 113 | from src.ui.simple_cli import main as simple_main 114 | simple_main() 115 | else: 116 | # Import and launch modern CLI (default) 117 | from src.ui.modern_cli import app 118 | app() 119 | except KeyboardInterrupt: 120 | if HAS_RICH and console: 121 | console.print("\n[yellow]👋 Trading system shutdown complete[/yellow]") 122 | else: 123 | print("\n👋 Trading system shutdown complete") 124 | except Exception as e: 125 | if HAS_RICH and console: 126 | console.print(f"\n[red]💥 Unexpected error: {e}[/red]") 127 | else: 128 | print(f"\n💥 Unexpected error: {e}") 129 | sys.exit(1) 130 | finally: 131 | # Restore original sys.argv 132 | sys.argv = original_argv 133 | 134 | if __name__ == '__main__': 135 | # Use Python 3.12's improved startup performance 136 | main() -------------------------------------------------------------------------------- /src/data/macro_provider.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | from typing import Dict, Any, Optional 4 | from datetime import datetime, timedelta 5 | from config import Config 6 | 7 | class MacroDataProvider: 8 | def __init__(self): 9 | if not Config.validate_fred_config(): 10 | raise ValueError("FRED API key not configured") 11 | 12 | self.api_key = Config.FRED_API_KEY 13 | self.base_url = "https://api.stlouisfed.org/fred" 14 | 15 | def get_series_data(self, series_id: str, start_date: Optional[datetime] = None, 16 | end_date: Optional[datetime] = None) -> pd.DataFrame: 17 | if start_date is None: 18 | start_date = datetime.now() - timedelta(days=365) 19 | if end_date is None: 20 | end_date = datetime.now() 21 | 22 | url = f"{self.base_url}/series/observations" 23 | params = { 24 | 'series_id': series_id, 25 | 'api_key': self.api_key, 26 | 'file_type': 'json', 27 | 'observation_start': start_date.strftime('%Y-%m-%d'), 28 | 'observation_end': end_date.strftime('%Y-%m-%d') 29 | } 30 | 31 | try: 32 | response = requests.get(url, params=params) 33 | response.raise_for_status() 34 | data = response.json() 35 | 36 | observations = data.get('observations', []) 37 | df_data = [] 38 | 39 | for obs in observations: 40 | if obs['value'] != '.': 41 | df_data.append({ 42 | 'date': pd.to_datetime(obs['date']), 43 | 'value': float(obs['value']) 44 | }) 45 | 46 | df = pd.DataFrame(df_data) 47 | if not df.empty: 48 | df.set_index('date', inplace=True) 49 | return df 50 | 51 | except Exception as e: 52 | print(f"Error fetching FRED data for {series_id}: {e}") 53 | return pd.DataFrame() 54 | 55 | def get_gdp_data(self) -> pd.DataFrame: 56 | return self.get_series_data('GDP') 57 | 58 | def get_unemployment_rate(self) -> pd.DataFrame: 59 | return self.get_series_data('UNRATE') 60 | 61 | def get_inflation_rate(self) -> pd.DataFrame: 62 | return self.get_series_data('CPIAUCSL') 63 | 64 | def get_federal_funds_rate(self) -> pd.DataFrame: 65 | return self.get_series_data('FEDFUNDS') 66 | 67 | def get_10_year_treasury(self) -> pd.DataFrame: 68 | return self.get_series_data('GS10') 69 | 70 | def get_sp500_data(self) -> pd.DataFrame: 71 | return self.get_series_data('SP500') 72 | 73 | def get_vix_data(self) -> pd.DataFrame: 74 | return self.get_series_data('VIXCLS') 75 | 76 | def get_dollar_index(self) -> pd.DataFrame: 77 | return self.get_series_data('DTWEXBGS') 78 | 79 | def get_economic_indicators(self) -> Dict[str, pd.DataFrame]: 80 | indicators = { 81 | 'GDP': self.get_gdp_data(), 82 | 'Unemployment_Rate': self.get_unemployment_rate(), 83 | 'Inflation_Rate': self.get_inflation_rate(), 84 | 'Federal_Funds_Rate': self.get_federal_funds_rate(), 85 | 'Treasury_10Y': self.get_10_year_treasury(), 86 | 'SP500': self.get_sp500_data(), 87 | 'VIX': self.get_vix_data(), 88 | 'Dollar_Index': self.get_dollar_index() 89 | } 90 | return indicators 91 | 92 | def get_series_info(self, series_id: str) -> Dict[str, Any]: 93 | url = f"{self.base_url}/series" 94 | params = { 95 | 'series_id': series_id, 96 | 'api_key': self.api_key, 97 | 'file_type': 'json' 98 | } 99 | 100 | try: 101 | response = requests.get(url, params=params) 102 | response.raise_for_status() 103 | data = response.json() 104 | 105 | if 'seriess' in data and len(data['seriess']) > 0: 106 | series_info = data['seriess'][0] 107 | return { 108 | 'id': series_info.get('id'), 109 | 'title': series_info.get('title'), 110 | 'units': series_info.get('units'), 111 | 'frequency': series_info.get('frequency'), 112 | 'seasonal_adjustment': series_info.get('seasonal_adjustment'), 113 | 'last_updated': series_info.get('last_updated'), 114 | 'observation_start': series_info.get('observation_start'), 115 | 'observation_end': series_info.get('observation_end') 116 | } 117 | return {} 118 | 119 | except Exception as e: 120 | print(f"Error fetching series info for {series_id}: {e}") 121 | return {} -------------------------------------------------------------------------------- /README_WHEEL.md: -------------------------------------------------------------------------------- 1 | # 🎯 Wheel Strategy Dashboard Guide 2 | 3 | ## Quick Start 4 | 5 | ### Launch the Wheel Dashboard 6 | ```bash 7 | # Launch wheel strategy dashboard 8 | python dashboard.py --wheel 9 | ``` 10 | 11 | The dashboard will open at: **http://localhost:3721** 12 | 13 | ## Dashboard Overview 14 | 15 | The Wheel Strategy Dashboard provides comprehensive tools for managing the wheel options strategy: 16 | 17 | ### 🎯 Overview Tab 18 | - Strategy performance metrics 19 | - Wheel cycle visualization 20 | - Recent activity summary 21 | - Key performance indicators 22 | 23 | ### 📊 Signals Tab 24 | - Real-time signal generation 25 | - Signal filtering and analysis 26 | - Confidence scoring 27 | - Auto-refresh capabilities 28 | 29 | ### 📈 Positions Tab 30 | - Current option positions 31 | - Assignment risk monitoring 32 | - Profit/loss tracking 33 | - Position management tools 34 | 35 | ### ⚙️ Settings Tab 36 | - Strategy parameter configuration 37 | - Risk management controls 38 | - Advanced settings 39 | - Configuration export/import 40 | 41 | ### 📚 Education Tab 42 | - Wheel strategy explanation 43 | - Parameter guides 44 | - Example scenarios 45 | - Risk considerations 46 | 47 | ## Configuration 48 | 49 | ### Default Configuration 50 | The dashboard uses `config/wheel_config.json` for default parameters: 51 | 52 | ```json 53 | { 54 | "max_risk": 80000, 55 | "position_size_pct": 0.08, 56 | "put_delta_min": 0.15, 57 | "put_delta_max": 0.30, 58 | "min_yield": 0.04, 59 | "dte_min": 7, 60 | "dte_max": 45 61 | } 62 | ``` 63 | 64 | ### Key Parameters Explained 65 | 66 | | Parameter | Description | Typical Range | 67 | |-----------|-------------|---------------| 68 | | `max_risk` | Maximum total risk exposure ($) | $10,000 - $500,000 | 69 | | `position_size_pct` | Position size as % of portfolio | 5% - 15% | 70 | | `put_delta_min/max` | Assignment probability range | 0.15 - 0.35 | 71 | | `min_yield` | Minimum acceptable yield | 3% - 8% | 72 | | `dte_min/max` | Days to expiration range | 7 - 45 days | 73 | 74 | ## Workflow 75 | 76 | ### 1. 🎯 Configure Strategy 77 | 1. Open Settings tab 78 | 2. Adjust parameters for your risk tolerance 79 | 3. Update strategy configuration 80 | 81 | ### 2. 📊 Generate Signals 82 | 1. Go to Signals tab 83 | 2. Click "Generate Signals" 84 | 3. Review signal quality and confidence 85 | 86 | ### 3. 📈 Monitor Positions 87 | 1. Check Positions tab 88 | 2. Monitor assignment risk 89 | 3. Manage profit-taking opportunities 90 | 91 | ### 4. 🔄 Manage Cycle 92 | 1. Close profitable positions 93 | 2. Handle assignments 94 | 3. Roll threatened positions 95 | 4. Restart with new signals 96 | 97 | ## Command Line Alternatives 98 | 99 | ### CLI Interface 100 | ```bash 101 | # Generate config template 102 | python wheel_cli.py --generate-config 103 | 104 | # Run strategy with CLI 105 | python wheel_cli.py --run --verbose 106 | 107 | # Show strategy information 108 | python wheel_cli.py --info 109 | ``` 110 | 111 | ### Example Scripts 112 | ```bash 113 | # Test the strategy 114 | python examples/wheel_strategy_example.py 115 | ``` 116 | 117 | ## Integration with Main System 118 | 119 | The wheel dashboard integrates with your existing GaussWorldTrader system: 120 | 121 | - **Uses your watchlist**: Reads symbols from `watchlist.json` 122 | - **Portfolio integration**: Works with existing portfolio system 123 | - **Risk management**: Follows your configured risk limits 124 | - **Data providers**: Uses existing Alpaca data integration 125 | 126 | ## Safety Features 127 | 128 | ### Built-in Risk Management 129 | - ✅ Position size limits 130 | - ✅ Maximum risk exposure 131 | - ✅ Assignment probability monitoring 132 | - ✅ Profit-taking automation 133 | - ✅ Emergency position closure 134 | 135 | ### Educational Content 136 | - ✅ Strategy explanation 137 | - ✅ Parameter guidance 138 | - ✅ Risk warnings 139 | - ✅ Example scenarios 140 | 141 | ## Troubleshooting 142 | 143 | ### Common Issues 144 | 145 | **Dashboard won't start:** 146 | ```bash 147 | # Check if Streamlit is installed 148 | pip install streamlit 149 | 150 | # Verify file exists 151 | ls src/ui/wheel_dashboard.py 152 | ``` 153 | 154 | **No signals generated:** 155 | - Check your watchlist has symbols 156 | - Verify strategy parameters aren't too restrictive 157 | - Ensure market is open (for live data) 158 | 159 | **Configuration issues:** 160 | ```bash 161 | # Regenerate config 162 | python wheel_cli.py --generate-config 163 | ``` 164 | 165 | ## Next Steps 166 | 167 | 1. **Paper Trading**: Start with small position sizes 168 | 2. **Live Integration**: Connect to Alpaca for real option data 169 | 3. **Backtesting**: Test historical performance 170 | 4. **Customization**: Adjust parameters based on results 171 | 172 | ## Support 173 | 174 | For issues or questions: 175 | - Check the Education tab in the dashboard 176 | - Review the wheel strategy documentation 177 | - Test with the CLI tools first 178 | - Start with paper trading 179 | 180 | --- 181 | 182 | **⚠️ Important**: The wheel strategy involves significant risk. Always understand the strategy fully and start with small positions before scaling up. -------------------------------------------------------------------------------- /src/data/fred_provider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Federal Reserve Economic Data (FRED) API provider 3 | """ 4 | 5 | import os 6 | import pandas as pd 7 | from typing import Dict, List, Any 8 | import logging 9 | 10 | try: 11 | from fredapi import Fred 12 | except ImportError: 13 | Fred = None 14 | 15 | 16 | class FREDProvider: 17 | """Federal Reserve Economic Data (FRED) API provider""" 18 | 19 | def __init__(self, api_key: str = None): 20 | self.api_key = api_key or os.getenv('FRED_API_KEY') 21 | self.logger = logging.getLogger(__name__) 22 | 23 | if not self.api_key: 24 | self.logger.warning("FRED API key not provided") 25 | self.client = None 26 | elif Fred is None: 27 | self.logger.error("fredapi library not installed. Install with: pip install fredapi") 28 | self.client = None 29 | else: 30 | try: 31 | self.client = Fred(api_key=self.api_key) 32 | except Exception as e: 33 | self.logger.error(f"Error initializing FRED client: {e}") 34 | self.client = None 35 | 36 | def get_series_data(self, series_id: str, 37 | start_date: str = None, 38 | end_date: str = None) -> pd.DataFrame: 39 | """Get economic data series from FRED""" 40 | if not self.client: 41 | return pd.DataFrame({"error": ["API client not configured"]}) 42 | 43 | try: 44 | data = self.client.get_series( 45 | series_id, 46 | observation_start=start_date, 47 | observation_end=end_date 48 | ) 49 | 50 | # Convert to DataFrame with consistent format 51 | df = pd.DataFrame({'value': data}) 52 | df.index.name = 'date' 53 | 54 | return df 55 | 56 | except Exception as e: 57 | self.logger.error(f"Error fetching FRED series {series_id}: {e}") 58 | return pd.DataFrame({"error": [str(e)]}) 59 | 60 | def get_gdp_data(self, start_date: str = None) -> pd.DataFrame: 61 | """Get GDP data""" 62 | return self.get_series_data('GDP', start_date) 63 | 64 | def get_unemployment_rate(self, start_date: str = None) -> pd.DataFrame: 65 | """Get unemployment rate""" 66 | return self.get_series_data('UNRATE', start_date) 67 | 68 | def get_inflation_rate(self, start_date: str = None) -> pd.DataFrame: 69 | """Get CPI inflation rate""" 70 | return self.get_series_data('CPIAUCSL', start_date) 71 | 72 | def get_federal_funds_rate(self, start_date: str = None) -> pd.DataFrame: 73 | """Get Federal Funds Rate""" 74 | return self.get_series_data('FEDFUNDS', start_date) 75 | 76 | def get_treasury_yield(self, maturity: str = '10Y', 77 | start_date: str = None) -> pd.DataFrame: 78 | """Get Treasury yield rates""" 79 | series_mapping = { 80 | '3M': 'TB3MS', 81 | '6M': 'TB6MS', 82 | '1Y': 'GS1', 83 | '2Y': 'GS2', 84 | '5Y': 'GS5', 85 | '10Y': 'GS10', 86 | '30Y': 'GS30' 87 | } 88 | 89 | series_id = series_mapping.get(maturity, 'GS10') 90 | return self.get_series_data(series_id, start_date) 91 | 92 | def get_economic_indicators(self, start_date: str = None) -> Dict[str, pd.DataFrame]: 93 | """Get key economic indicators""" 94 | indicators = { 95 | 'GDP': self.get_gdp_data(start_date), 96 | 'Unemployment': self.get_unemployment_rate(start_date), 97 | 'Inflation': self.get_inflation_rate(start_date), 98 | 'Federal_Funds_Rate': self.get_federal_funds_rate(start_date), 99 | 'Treasury_10Y': self.get_treasury_yield('10Y', start_date) 100 | } 101 | 102 | return indicators 103 | 104 | def search_series(self, search_text: str, limit: int = 10) -> List[Dict[str, Any]]: 105 | """Search for economic data series""" 106 | if not self.client: 107 | return [{"error": "API client not configured"}] 108 | 109 | try: 110 | # Use fredapi's search functionality 111 | search_results = self.client.search(search_text, limit=limit) 112 | 113 | # Convert to list of dictionaries for consistency 114 | result_list = [] 115 | for idx, row in search_results.iterrows(): 116 | result_list.append({ 117 | 'id': row.get('id', ''), 118 | 'title': row.get('title', ''), 119 | 'observation_start': row.get('observation_start', ''), 120 | 'observation_end': row.get('observation_end', ''), 121 | 'frequency': row.get('frequency', ''), 122 | 'units': row.get('units', ''), 123 | 'seasonal_adjustment': row.get('seasonal_adjustment', ''), 124 | 'notes': row.get('notes', '') 125 | }) 126 | 127 | return result_list 128 | 129 | except Exception as e: 130 | self.logger.error(f"Error searching FRED series: {e}") 131 | return [{"error": str(e)}] -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=68.0", "wheel>=0.41.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "gauss-world-trader" 7 | version = "1.1.0" 8 | description = "Gauss World Trader - A comprehensive Python 3.12+ quantitative trading system named after Carl Friedrich Gauss" 9 | authors = [ 10 | {name = "Zexun Chen", email = "zexun.chen@gauss.world"} 11 | ] 12 | readme = "README.md" 13 | license = {file = "LICENSE"} 14 | requires-python = ">=3.12" 15 | keywords = ["trading", "finance", "quantitative", "backtesting", "alpaca", "gauss", "world-trader"] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Intended Audience :: Financial and Insurance Industry", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.12", 23 | "Topic :: Office/Business :: Financial :: Investment", 24 | "Topic :: Scientific/Engineering :: Information Analysis", 25 | "Typing :: Typed" 26 | ] 27 | 28 | dependencies = [ 29 | "pandas>=2.1.0", 30 | "numpy>=1.26.0", 31 | "requests>=2.31.0", 32 | "python-dotenv>=1.0.0", 33 | "ta-lib>=0.4.28", 34 | "matplotlib>=3.8.0", 35 | "plotly>=5.17.0", 36 | "seaborn>=0.13.0", 37 | "streamlit>=1.28.0", 38 | "yfinance>=0.2.20", 39 | "fredapi>=0.5.1", 40 | "finnhub-python>=2.4.20", 41 | "scipy>=1.11.0", 42 | "scikit-learn>=1.3.0", 43 | "sqlalchemy>=2.0.0", 44 | "loguru>=0.7.0", 45 | "rich>=13.6.0", 46 | "typer>=0.9.0", 47 | "aiohttp>=3.9.0", 48 | "websockets>=12.0", 49 | "pydantic>=2.4.0", 50 | "httpx>=0.25.0" 51 | ] 52 | 53 | [project.optional-dependencies] 54 | dev = [ 55 | "pytest>=7.4.0", 56 | "pytest-cov>=4.1.0", 57 | "pytest-asyncio>=0.21.0", 58 | "mypy>=1.6.0", 59 | "black>=23.9.0", 60 | "ruff>=0.1.0", 61 | "pre-commit>=3.5.0" 62 | ] 63 | docs = [ 64 | "sphinx>=7.2.0", 65 | "sphinx-rtd-theme>=1.3.0" 66 | ] 67 | performance = [ 68 | "numba>=0.58.0", 69 | "cython>=3.0.0" 70 | ] 71 | all = [ 72 | "gauss-world-trader[dev,docs,performance]" 73 | ] 74 | 75 | [project.urls] 76 | Homepage = "https://github.com/Magica-Chen/GaussWorldTrader" 77 | Documentation = "https://github.com/Magica-Chen/GaussWorldTrader/wiki" 78 | Repository = "https://github.com/Magica-Chen/GaussWorldTrader.git" 79 | Issues = "https://github.com/Magica-Chen/GaussWorldTrader/issues" 80 | 81 | [project.scripts] 82 | trading-cli = "main:main" 83 | trading-dashboard = "dashboard:main" 84 | 85 | [tool.setuptools] 86 | packages = ["src", "config"] 87 | 88 | [tool.setuptools.package-data] 89 | "*" = ["*.toml", "*.yaml", "*.yml", "*.json"] 90 | 91 | # Black configuration (Python 3.12 optimized) 92 | [tool.black] 93 | target-version = ['py312'] 94 | line-length = 100 95 | skip-string-normalization = true 96 | extend-exclude = ''' 97 | /( 98 | \.eggs 99 | | \.git 100 | | \.hg 101 | | \.mypy_cache 102 | | \.tox 103 | | \.venv 104 | | _build 105 | | buck-out 106 | | build 107 | | dist 108 | )/ 109 | ''' 110 | 111 | # Ruff configuration (faster linter) 112 | [tool.ruff] 113 | target-version = "py312" 114 | line-length = 100 115 | select = [ 116 | "E", # pycodestyle errors 117 | "W", # pycodestyle warnings 118 | "F", # pyflakes 119 | "I", # isort 120 | "B", # flake8-bugbear 121 | "C4", # flake8-comprehensions 122 | "UP", # pyupgrade 123 | "ARG", # flake8-unused-arguments 124 | "SIM", # flake8-simplify 125 | "TCH", # flake8-type-checking 126 | ] 127 | ignore = [ 128 | "E501", # line too long, handled by black 129 | "B008", # do not perform function calls in argument defaults 130 | "C901", # too complex 131 | "ARG002", # unused method argument 132 | ] 133 | unfixable = ["B"] 134 | 135 | [tool.ruff.per-file-ignores] 136 | "__init__.py" = ["E402", "F401", "F403", "F811"] 137 | "tests/**/*" = ["ARG", "FBT"] 138 | 139 | [tool.ruff.isort] 140 | known-first-party = ["src", "config"] 141 | 142 | # MyPy configuration (Python 3.12 features) 143 | [tool.mypy] 144 | python_version = "3.12" 145 | warn_return_any = true 146 | warn_unused_configs = true 147 | disallow_untyped_defs = true 148 | disallow_incomplete_defs = true 149 | check_untyped_defs = true 150 | disallow_untyped_decorators = true 151 | warn_redundant_casts = true 152 | warn_unused_ignores = true 153 | warn_no_return = true 154 | warn_unreachable = true 155 | strict_equality = true 156 | extra_checks = true 157 | 158 | [[tool.mypy.overrides]] 159 | module = [ 160 | "alpaca.*", 161 | "ta", 162 | "fredapi.*", 163 | "yfinance.*", 164 | "numba.*", 165 | "finnhub.*" 166 | ] 167 | ignore_missing_imports = true 168 | 169 | # Pytest configuration 170 | [tool.pytest.ini_options] 171 | minversion = "7.0" 172 | addopts = "-ra -q --strict-markers --strict-config" 173 | testpaths = ["tests"] 174 | python_files = ["test_*.py", "*_test.py"] 175 | python_classes = ["Test*"] 176 | python_functions = ["test_*"] 177 | markers = [ 178 | "slow: marks tests as slow (deselect with '-m \"not slow\"')", 179 | "integration: marks tests as integration tests", 180 | "unit: marks tests as unit tests" 181 | ] 182 | asyncio_mode = "auto" 183 | 184 | # Coverage configuration 185 | [tool.coverage.run] 186 | source = ["src"] 187 | omit = [ 188 | "*/tests/*", 189 | "*/test_*.py", 190 | "setup.py", 191 | "*/migrations/*" 192 | ] 193 | 194 | [tool.coverage.report] 195 | precision = 2 196 | show_missing = true 197 | skip_covered = false 198 | exclude_lines = [ 199 | "pragma: no cover", 200 | "def __repr__", 201 | "raise AssertionError", 202 | "raise NotImplementedError", 203 | "if __name__ == .__main__.:", 204 | "if TYPE_CHECKING:" 205 | ] -------------------------------------------------------------------------------- /tests/test_dashboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Test script for the Advanced Dashboard 4 | 5 | Verify that all dashboard components work correctly 6 | """ 7 | 8 | import sys 9 | import os 10 | from pathlib import Path 11 | 12 | # Add project root to path 13 | project_root = Path(__file__).parent 14 | sys.path.insert(0, str(project_root)) 15 | 16 | def test_imports(): 17 | """Test all required imports for the dashboard""" 18 | print("🧪 Testing Dashboard Imports...") 19 | 20 | try: 21 | import streamlit as st 22 | print("✅ Streamlit imported successfully") 23 | except ImportError: 24 | print("❌ Streamlit not found. Install with: pip install streamlit") 25 | return False 26 | 27 | try: 28 | import plotly.graph_objects as go 29 | import plotly.express as px 30 | print("✅ Plotly imported successfully") 31 | except ImportError: 32 | print("❌ Plotly not found. Install with: pip install plotly") 33 | return False 34 | 35 | try: 36 | import pandas as pd 37 | import numpy as np 38 | print("✅ Pandas and NumPy imported successfully") 39 | except ImportError: 40 | print("❌ Pandas/NumPy not found. Install with: pip install pandas numpy") 41 | return False 42 | 43 | try: 44 | import pytz 45 | print("✅ PyTZ imported successfully") 46 | except ImportError: 47 | print("❌ PyTZ not found. Install with: pip install pytz") 48 | return False 49 | 50 | return True 51 | 52 | def test_project_modules(): 53 | """Test project-specific module imports""" 54 | print("\n🔧 Testing Project Module Imports...") 55 | 56 | try: 57 | from src.account.account_manager import AccountManager 58 | print("✅ AccountManager imported successfully") 59 | except ImportError as e: 60 | print(f"⚠️ AccountManager import warning: {e}") 61 | 62 | try: 63 | from src.account.position_manager import PositionManager 64 | print("✅ PositionManager imported successfully") 65 | except ImportError as e: 66 | print(f"⚠️ PositionManager import warning: {e}") 67 | 68 | try: 69 | from src.account.order_manager import OrderManager 70 | print("✅ OrderManager imported successfully") 71 | except ImportError as e: 72 | print(f"⚠️ OrderManager import warning: {e}") 73 | 74 | try: 75 | from src.stock_strategy.strategy_selector import get_strategy_selector 76 | print("✅ StrategySelector imported successfully") 77 | except ImportError as e: 78 | print(f"⚠️ StrategySelector import warning: {e}") 79 | 80 | try: 81 | from src.agent.fundamental_analyzer import FundamentalAnalyzer 82 | print("✅ FundamentalAnalyzer imported successfully") 83 | except ImportError as e: 84 | print(f"⚠️ FundamentalAnalyzer import warning: {e}") 85 | 86 | return True 87 | 88 | def test_strategy_framework(): 89 | """Test the strategy framework functionality""" 90 | print("\n🎯 Testing Strategy Framework...") 91 | 92 | try: 93 | from src.stock_strategy.strategy_selector import get_strategy_selector 94 | 95 | selector = get_strategy_selector() 96 | strategies = selector.list_strategies() 97 | 98 | print(f"✅ Found {len(strategies)} available strategies:") 99 | for strategy in strategies: 100 | print(f" • {strategy}") 101 | 102 | # Test strategy creation 103 | if strategies: 104 | test_strategy = strategies[0] 105 | strategy_instance = selector.create_strategy(test_strategy) 106 | if strategy_instance: 107 | print(f"✅ Successfully created {test_strategy} strategy instance") 108 | 109 | # Test strategy info 110 | info = strategy_instance.get_strategy_info() 111 | print(f" Strategy type: {info.get('type', 'Unknown')}") 112 | print(f" Risk level: {info.get('risk_level', 'Unknown')}") 113 | else: 114 | print(f"⚠️ Failed to create {test_strategy} strategy instance") 115 | 116 | return True 117 | 118 | except Exception as e: 119 | print(f"❌ Strategy framework test failed: {e}") 120 | return False 121 | 122 | def test_dashboard_file(): 123 | """Test that the dashboard file exists and is valid""" 124 | print("\n📊 Testing Dashboard File...") 125 | 126 | dashboard_path = project_root / "src" / "ui" / "advanced_dashboard.py" 127 | 128 | if not dashboard_path.exists(): 129 | print(f"❌ Dashboard file not found: {dashboard_path}") 130 | return False 131 | 132 | print(f"✅ Dashboard file found: {dashboard_path}") 133 | 134 | # Try to import the dashboard module (basic syntax check) 135 | try: 136 | sys.path.insert(0, str(dashboard_path.parent)) 137 | import advanced_dashboard 138 | print("✅ Dashboard module imports correctly") 139 | 140 | # Check for main functions 141 | required_functions = [ 142 | 'main', 143 | 'render_account_tab', 144 | 'render_live_analysis_tab', 145 | 'render_backtesting_tab', 146 | 'render_trading_tab' 147 | ] 148 | 149 | for func_name in required_functions: 150 | if hasattr(advanced_dashboard, func_name): 151 | print(f"✅ Found function: {func_name}") 152 | else: 153 | print(f"⚠️ Missing function: {func_name}") 154 | 155 | return True 156 | 157 | except Exception as e: 158 | print(f"❌ Dashboard module import failed: {e}") 159 | return False 160 | 161 | def main(): 162 | """Run all dashboard tests""" 163 | print("🌍 Gauss World Trader - Dashboard Test Suite") 164 | print("=" * 60) 165 | 166 | all_passed = True 167 | 168 | # Run tests 169 | all_passed &= test_imports() 170 | all_passed &= test_project_modules() 171 | all_passed &= test_strategy_framework() 172 | all_passed &= test_dashboard_file() 173 | 174 | print("\n" + "=" * 60) 175 | 176 | if all_passed: 177 | print("✅ All dashboard tests passed!") 178 | print("\n🚀 You can now run the dashboard with:") 179 | print(" python run_dashboard.py") 180 | print("\n📋 Or manually with:") 181 | print(" streamlit run src/ui/advanced_dashboard.py --server.port=3721") 182 | else: 183 | print("⚠️ Some tests failed. Please check the output above.") 184 | print("\n📥 Install missing dependencies with:") 185 | print(" pip install -r requirements_dashboard.txt") 186 | 187 | print("\n🌟 Dashboard Features Available:") 188 | print(" • Account Management (Positions, Orders, P&L)") 189 | print(" • Live Market Analysis (Technical & Fundamental)") 190 | print(" • Strategy Backtesting (8+ Trading Strategies)") 191 | print(" • Active Trading Interface") 192 | print(" • Market Overview and Watchlists") 193 | 194 | if __name__ == "__main__": 195 | main() -------------------------------------------------------------------------------- /src/ui/simple_cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple CLI interface with Python 3.12 compatibility 3 | Uses core CLI abstraction to eliminate code duplication 4 | """ 5 | from __future__ import annotations 6 | 7 | from typing import Annotated 8 | 9 | from src.ui.core_cli import BaseCLI, SimpleFallbackConcrete, HAS_RICH 10 | 11 | if HAS_RICH: 12 | import typer 13 | from src.trade import Portfolio 14 | from src.stock_strategy import MomentumStrategy 15 | from src.utils.timezone_utils import now_et 16 | from datetime import timedelta 17 | from src.data import AlpacaDataProvider 18 | 19 | 20 | class SimpleCLI(BaseCLI): 21 | """Simple CLI implementation using core abstraction.""" 22 | 23 | def __init__(self): 24 | super().__init__( 25 | app_name="simple-trading", 26 | app_help="🚀 Simple Quantitative Trading System" 27 | ) 28 | self.setup_commands() 29 | 30 | def setup_commands(self) -> None: 31 | """Setup CLI commands specific to simple interface.""" 32 | if not self.has_rich or not self.app: 33 | return 34 | 35 | @self.app.command("account-info") 36 | def account_info(): 37 | """💼 Display account information""" 38 | if not self.display_account_info(): 39 | self.exit_with_error() 40 | 41 | @self.app.command("validate-config") 42 | def validate_config_cmd(): 43 | """🔧 Validate configuration""" 44 | if not self.validate_config_impl(): 45 | self.exit_with_error() 46 | 47 | @self.app.command("check-positions") 48 | def check_positions_cmd(): 49 | """📈 Check current positions and recent orders""" 50 | self.handle_portfolio_command("check-positions") 51 | 52 | @self.app.command("watchlist-trade") 53 | def watchlist_trade_cmd(): 54 | """🎯 Analyze watchlist and execute trades""" 55 | self.handle_portfolio_command("watchlist-trade") 56 | 57 | @self.app.command("get-data") 58 | def get_data( 59 | symbol: Annotated[str, typer.Argument(help="Stock symbol")], 60 | days: Annotated[int, typer.Option("--days", "-d", help="Days back")] = 30 61 | ): 62 | """📊 Fetch market data""" 63 | if not self.display_market_data(symbol, days): 64 | self.exit_with_error() 65 | 66 | @self.app.command("run-strategy") 67 | def run_strategy( 68 | symbols: Annotated[list[str], typer.Argument(help="Stock symbols")], 69 | dry_run: Annotated[bool, typer.Option("--dry-run", help="Simulate only")] = True 70 | ): 71 | """🧠 Run momentum strategy""" 72 | self._run_momentum_strategy(symbols, dry_run) 73 | 74 | @self.app.callback() 75 | def main_callback( 76 | version: Annotated[bool, typer.Option("--version", help="Show version")] = False 77 | ): 78 | """🚀 Simple Quantitative Trading System""" 79 | if version: 80 | self.print_message("Simple Trading System v2.0.0 (Python 3.12 Compatible)", "blue") 81 | self.exit_with_error(0) 82 | 83 | def _run_momentum_strategy(self, symbols: list[str], dry_run: bool) -> None: 84 | """Run momentum strategy on given symbols.""" 85 | try: 86 | self.print_info(f"Running momentum strategy on: {', '.join(symbols)}") 87 | 88 | provider = AlpacaDataProvider() 89 | strategy = MomentumStrategy() 90 | 91 | historical_data = {} 92 | current_prices = {} 93 | 94 | for symbol in symbols: 95 | symbol = symbol.upper() 96 | current_time = now_et() 97 | start_date = current_time - timedelta(days=100) 98 | 99 | data = provider.get_bars(symbol, '1Day', start_date) 100 | if not data.empty: 101 | historical_data[symbol] = data 102 | current_prices[symbol] = data['close'].iloc[-1] 103 | 104 | if not historical_data: 105 | self.print_error("No data retrieved for any symbols") 106 | return 107 | 108 | portfolio = Portfolio() 109 | signals = strategy.generate_signals( 110 | current_date=now_et(), 111 | current_prices=current_prices, 112 | current_data={}, 113 | historical_data=historical_data, 114 | portfolio=portfolio 115 | ) 116 | 117 | if signals: 118 | if self.has_rich and self.console: 119 | from rich.table import Table 120 | table = Table(title="🧠 Strategy Signals") 121 | table.add_column("Symbol", style="cyan") 122 | table.add_column("Action", style="green") 123 | table.add_column("Quantity", style="yellow") 124 | table.add_column("Confidence", style="magenta") 125 | table.add_column("Reason", style="white") 126 | 127 | for signal in signals: 128 | confidence = f"{signal.get('confidence', 0):.1%}" 129 | reason = signal.get('reason', 'N/A') 130 | if len(reason) > 50: 131 | reason = reason[:50] + "..." 132 | 133 | table.add_row( 134 | signal['symbol'], 135 | signal['action'].upper(), 136 | str(signal['quantity']), 137 | confidence, 138 | reason 139 | ) 140 | 141 | self.console.print(table) 142 | else: 143 | for signal in signals: 144 | print(f"{signal['symbol']}: {signal['action']} {signal['quantity']} " 145 | f"(confidence: {signal.get('confidence', 0):.1%})") 146 | 147 | if dry_run: 148 | self.print_info("DRY RUN: No actual trades executed") 149 | else: 150 | self.print_warning("Live trading requires manual implementation") 151 | else: 152 | self.print_warning("No signals generated") 153 | 154 | except Exception as e: 155 | self.print_error(str(e)) 156 | self.exit_with_error() 157 | 158 | def run(self) -> None: 159 | """Run the simple CLI.""" 160 | if self.has_rich and self.app: 161 | self.app() 162 | else: 163 | fallback = SimpleFallbackConcrete("fallback", "Fallback CLI") 164 | fallback.run_interactive() 165 | 166 | 167 | # Global app instance for backward compatibility 168 | if HAS_RICH: 169 | _cli_instance = SimpleCLI() 170 | app = _cli_instance.app 171 | 172 | def main(): 173 | _cli_instance.run() 174 | else: 175 | app = None 176 | 177 | def main(): 178 | fallback = SimpleFallbackConcrete("fallback", "Fallback CLI") 179 | fallback.run_interactive() 180 | 181 | 182 | if __name__ == "__main__": 183 | main() -------------------------------------------------------------------------------- /src/data/finnhub_provider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Finnhub API provider for financial market data and news 3 | """ 4 | 5 | import os 6 | import finnhub 7 | from typing import Dict, List, Any 8 | from datetime import datetime, timedelta 9 | import logging 10 | 11 | 12 | class FinnhubProvider: 13 | """Finnhub API provider for market data and news""" 14 | 15 | def __init__(self, api_key: str = None): 16 | self.api_key = api_key or os.getenv('FINNHUB_API_KEY') 17 | self.logger = logging.getLogger(__name__) 18 | 19 | if not self.api_key: 20 | self.logger.warning("Finnhub API key not provided") 21 | self.client = None 22 | else: 23 | self.client = finnhub.Client(api_key=self.api_key) 24 | 25 | def get_company_profile(self, symbol: str) -> Dict[str, Any]: 26 | """Get company profile information""" 27 | if not self.client: 28 | return {"error": "API key not configured"} 29 | 30 | try: 31 | return self.client.company_profile2(symbol=symbol) 32 | except Exception as e: 33 | self.logger.error(f"Error fetching company profile for {symbol}: {e}") 34 | return {"error": str(e)} 35 | 36 | def get_basic_financials(self, symbol: str) -> Dict[str, Any]: 37 | """Get basic financial metrics""" 38 | if not self.client: 39 | return {"error": "API key not configured"} 40 | 41 | try: 42 | return self.client.company_basic_financials(symbol, 'all') 43 | except Exception as e: 44 | self.logger.error(f"Error fetching financials for {symbol}: {e}") 45 | return {"error": str(e)} 46 | 47 | def get_earnings_calendar(self, symbol: str = None, 48 | from_date: str = None, 49 | to_date: str = None) -> Dict[str, Any]: 50 | """Get earnings calendar""" 51 | if not self.client: 52 | return {"error": "API key not configured"} 53 | 54 | try: 55 | return self.client.earnings_calendar(_from=from_date, to=to_date, symbol=symbol) 56 | except Exception as e: 57 | self.logger.error(f"Error fetching earnings calendar: {e}") 58 | return {"error": str(e)} 59 | 60 | def get_company_news(self, symbol: str, 61 | from_date: str = None, 62 | to_date: str = None) -> List[Dict[str, Any]]: 63 | """Get company news""" 64 | if not self.client: 65 | return [{"error": "API key not configured"}] 66 | 67 | if not from_date: 68 | from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') 69 | if not to_date: 70 | to_date = datetime.now().strftime('%Y-%m-%d') 71 | 72 | try: 73 | return self.client.company_news(symbol, _from=from_date, to=to_date) 74 | except Exception as e: 75 | self.logger.error(f"Error fetching news for {symbol}: {e}") 76 | return [{"error": str(e)}] 77 | 78 | def get_market_news(self, category: str = 'general') -> List[Dict[str, Any]]: 79 | """Get general market news""" 80 | if not self.client: 81 | return [{"error": "API key not configured"}] 82 | 83 | try: 84 | return self.client.general_news(category, min_id=0) 85 | except Exception as e: 86 | self.logger.error(f"Error fetching market news: {e}") 87 | return [{"error": str(e)}] 88 | 89 | def get_recommendation_trends(self, symbol: str) -> Dict[str, Any]: 90 | """Get analyst recommendation trends""" 91 | if not self.client: 92 | return {"error": "API key not configured"} 93 | 94 | try: 95 | return self.client.recommendation_trends(symbol) 96 | except Exception as e: 97 | self.logger.error(f"Error fetching recommendations for {symbol}: {e}") 98 | return {"error": str(e)} 99 | 100 | def get_price_target(self, symbol: str) -> Dict[str, Any]: 101 | """Get analyst price targets""" 102 | if not self.client: 103 | return {"error": "API key not configured"} 104 | 105 | try: 106 | return self.client.price_target(symbol) 107 | except Exception as e: 108 | self.logger.error(f"Error fetching price target for {symbol}: {e}") 109 | return {"error": str(e)} 110 | 111 | def get_quote(self, symbol: str) -> Dict[str, Any]: 112 | """Get real-time stock quote""" 113 | if not self.client: 114 | return {"error": "API key not configured"} 115 | 116 | try: 117 | return self.client.quote(symbol) 118 | except Exception as e: 119 | self.logger.error(f"Error fetching quote for {symbol}: {e}") 120 | return {"error": str(e)} 121 | 122 | def get_stock_candles(self, symbol: str, resolution: str = 'D', 123 | from_timestamp: int = None, 124 | to_timestamp: int = None) -> Dict[str, Any]: 125 | """Get stock price candles""" 126 | if not self.client: 127 | return {"error": "API key not configured"} 128 | 129 | if not from_timestamp: 130 | from_timestamp = int((datetime.now() - timedelta(days=30)).timestamp()) 131 | if not to_timestamp: 132 | to_timestamp = int(datetime.now().timestamp()) 133 | 134 | try: 135 | return self.client.stock_candles(symbol, resolution, from_timestamp, 136 | to_timestamp) 137 | except Exception as e: 138 | self.logger.error(f"Error fetching candles for {symbol}: {e}") 139 | return {"error": str(e)} 140 | 141 | def get_earnings_surprises(self, symbol: str, limit: int = 4) -> List[Dict[str, Any]]: 142 | """Get earnings surprises""" 143 | if not self.client: 144 | return [{"error": "API key not configured"}] 145 | 146 | try: 147 | return self.client.company_earnings(symbol, limit) 148 | except Exception as e: 149 | self.logger.error(f"Error fetching earnings for {symbol}: {e}") 150 | return [{"error": str(e)}] 151 | 152 | def get_insider_transactions(self, symbol: str) -> List[Dict[str, Any]]: 153 | """Get insider transactions""" 154 | if not self.client: 155 | return [{"error": "API key not configured"}] 156 | 157 | try: 158 | return self.client.stock_insider_transactions(symbol) 159 | except Exception as e: 160 | self.logger.error(f"Error fetching insider transactions for {symbol}: {e}") 161 | return [{"error": str(e)}] 162 | 163 | def get_insider_sentiment(self, symbol: str, 164 | from_date: str = None, 165 | to_date: str = None) -> Dict[str, Any]: 166 | """Get insider sentiment""" 167 | if not self.client: 168 | return {"error": "API key not configured"} 169 | 170 | if not from_date: 171 | from_date = (datetime.now() - timedelta(days=90)).strftime('%Y-%m-%d') 172 | if not to_date: 173 | to_date = datetime.now().strftime('%Y-%m-%d') 174 | 175 | try: 176 | return self.client.stock_insider_sentiment(symbol, from_date, to_date) 177 | except Exception as e: 178 | self.logger.error(f"Error fetching insider sentiment for {symbol}: {e}") 179 | return {"error": str(e)} -------------------------------------------------------------------------------- /dashboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Gauss World Trader - Dashboard Launcher 4 | 5 | Root-level dashboard launcher for easy access alongside main.py 6 | Supports simple and modern dashboard interfaces 7 | All dashboard implementations are in src/ui/ directory 8 | """ 9 | 10 | import sys 11 | import os 12 | from pathlib import Path 13 | import subprocess 14 | 15 | # Add project root to path 16 | project_root = Path(__file__).parent 17 | sys.path.insert(0, str(project_root)) 18 | 19 | def run_dashboard(mode="modern"): 20 | """Launch dashboard using streamlit run command""" 21 | # Set the project root 22 | project_root = Path(__file__).parent 23 | os.chdir(project_root) 24 | 25 | # Add project to Python path 26 | sys.path.insert(0, str(project_root)) 27 | 28 | # Mode-specific configurations 29 | if mode == "simple": 30 | dashboard_file = "src/ui/simple_dashboard.py" 31 | print("🔹 Starting Simple Dashboard via Streamlit...") 32 | elif mode == "wheel": 33 | dashboard_file = "src/ui/wheel_dashboard.py" 34 | print("🎯 Starting Wheel Strategy Dashboard via Streamlit...") 35 | else: # modern (default) 36 | dashboard_file = "src/ui/modern_dashboard.py" 37 | print("🌍 Starting Modern Dashboard via Streamlit...") 38 | 39 | try: 40 | # Launch using streamlit run command 41 | cmd = [ 42 | sys.executable, "-m", "streamlit", "run", 43 | dashboard_file, 44 | "--server.port=3721", 45 | "--server.address=localhost", 46 | "--theme.base=light", 47 | "--theme.primaryColor=#1f77b4", 48 | "--theme.backgroundColor=#ffffff", 49 | "--theme.secondaryBackgroundColor=#f0f2f6", 50 | "--theme.textColor=#262730" 51 | ] 52 | 53 | print(f"🚀 Dashboard will open at http://localhost:3721") 54 | print("🔄 Press Ctrl+C to stop the dashboard") 55 | print() 56 | 57 | subprocess.run(cmd) 58 | 59 | except KeyboardInterrupt: 60 | print("\n⏹️ Dashboard stopped by user") 61 | except FileNotFoundError: 62 | print("❌ Streamlit not found. Please install with: pip install streamlit") 63 | sys.exit(1) 64 | except Exception as e: 65 | print(f"❌ Error running dashboard: {e}") 66 | sys.exit(1) 67 | 68 | def launch_dashboard(mode="modern"): 69 | """Launch the dashboard with proper configuration""" 70 | # Set the project root 71 | project_root = Path(__file__).parent 72 | os.chdir(project_root) 73 | 74 | # Add project to Python path 75 | sys.path.insert(0, str(project_root)) 76 | 77 | # Mode-specific configurations 78 | if mode == "simple": 79 | dashboard_file = "src/ui/simple_dashboard.py" 80 | print("🔹 Starting Gauss World Trader - Simple Dashboard") 81 | print("=" * 60) 82 | print("Dashboard Features:") 83 | print("• 📈 Market Analysis with Technical Indicators") 84 | print("• 📊 Strategy Backtesting") 85 | print("• 💼 Account Overview") 86 | print("• 🔄 Trading Interface") 87 | print("• 📰 News & Sentiment Analysis") 88 | print("• ₿ Cryptocurrency Data") 89 | print("=" * 60) 90 | elif mode == "wheel": 91 | dashboard_file = "src/ui/wheel_dashboard.py" 92 | print("🎯 Starting Gauss World Trader - Wheel Strategy Dashboard") 93 | print("=" * 60) 94 | print("Dashboard Features:") 95 | print("• 🎯 Wheel Strategy Overview & Cycle Monitoring") 96 | print("• 📊 Real-time Signal Generation & Analysis") 97 | print("• 📈 Option Position Management & Risk Assessment") 98 | print("• ⚙️ Strategy Configuration & Parameter Tuning") 99 | print("• 📚 Educational Content & Strategy Explanation") 100 | print("• 🛡️ Risk Management & Assignment Monitoring") 101 | print("=" * 60) 102 | else: # modern (default) 103 | dashboard_file = "src/ui/modern_dashboard.py" 104 | print("🌍 Starting Gauss World Trader - Modern Dashboard") 105 | print("=" * 60) 106 | print("Dashboard Features:") 107 | print("• 📊 Market Overview (Indices, VIX, Sectors, Calendar, Crypto)") 108 | print("• 💼 Account Info (Account, Positions, Portfolio, Performance, Config)") 109 | print("• 🔍 Live Analysis (Symbol Analysis, Watchlist)") 110 | print("• 📈 Strategy Backtest (Quick Backtest, Strategy Comparison)") 111 | print("• ⚡ Trade & Order (Quick Trade, Active Orders, Order History)") 112 | print("• 📰 News & Report (Company News, Insider Activity, AI Reports)") 113 | print("=" * 60) 114 | 115 | try: 116 | # Launch Streamlit dashboard 117 | cmd = [ 118 | sys.executable, "-m", "streamlit", "run", 119 | dashboard_file, 120 | "--server.port=3721", 121 | "--server.address=localhost", # Fixed to localhost only 122 | "--theme.base=light", 123 | "--theme.primaryColor=#1f77b4", 124 | "--theme.backgroundColor=#ffffff", 125 | "--theme.secondaryBackgroundColor=#f0f2f6", 126 | "--theme.textColor=#262730" 127 | ] 128 | 129 | print(f"🚀 Launching dashboard on http://localhost:3721") 130 | print("📱 Open your browser and navigate to the URL above") 131 | print("🔄 Press Ctrl+C to stop the dashboard") 132 | print() 133 | 134 | subprocess.run(cmd) 135 | 136 | except KeyboardInterrupt: 137 | print("\n⏹️ Dashboard stopped by user") 138 | except FileNotFoundError: 139 | print("❌ Streamlit not found. Please install with: pip install streamlit") 140 | sys.exit(1) 141 | except Exception as e: 142 | print(f"❌ Error launching dashboard: {e}") 143 | sys.exit(1) 144 | 145 | if __name__ == "__main__": 146 | import argparse 147 | 148 | # Parse command line arguments 149 | parser = argparse.ArgumentParser( 150 | description='Gauss World Trader Dashboard', 151 | epilog=""" 152 | Examples: 153 | python dashboard.py # Launch modern dashboard (default) 154 | python dashboard.py --simple # Launch simple dashboard 155 | python dashboard.py --wheel # Launch wheel strategy dashboard 156 | python dashboard.py launch --wheel # Launch wheel dashboard with enhanced config 157 | """, 158 | formatter_class=argparse.RawDescriptionHelpFormatter 159 | ) 160 | parser.add_argument('command', nargs='?', default='run', choices=['launch', 'run'], 161 | help='Command to execute (launch or run)') 162 | parser.add_argument('--simple', action='store_true', 163 | help='Use simple dashboard interface (includes crypto, news, and technical analysis)') 164 | parser.add_argument('--modern', action='store_true', 165 | help='Use modern dashboard interface (default - redesigned navigation structure)') 166 | parser.add_argument('--wheel', action='store_true', 167 | help='Use wheel strategy dashboard interface (options trading focus)') 168 | 169 | args = parser.parse_args() 170 | 171 | # Determine dashboard mode 172 | if args.simple: 173 | mode = "simple" 174 | elif args.wheel: 175 | mode = "wheel" 176 | else: 177 | mode = "modern" # default 178 | 179 | # Execute command 180 | if args.command == "launch": 181 | # Launch mode - start streamlit with enhanced configuration 182 | launch_dashboard(mode) 183 | else: 184 | # Normal streamlit run mode - run dashboard directly 185 | run_dashboard(mode) -------------------------------------------------------------------------------- /src/trade/portfolio.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any, Optional 2 | import pandas as pd 3 | from datetime import datetime 4 | import logging 5 | 6 | class Portfolio: 7 | def __init__(self, initial_cash: float = 100000.0): 8 | self.initial_cash = initial_cash 9 | self.cash = initial_cash 10 | self.positions: Dict[str, Dict[str, Any]] = {} 11 | self.transactions: List[Dict[str, Any]] = [] 12 | self.performance_history: List[Dict[str, Any]] = [] 13 | 14 | def add_position(self, symbol: str, quantity: int, price: float, 15 | timestamp: Optional[datetime] = None): 16 | if timestamp is None: 17 | timestamp = datetime.now() 18 | 19 | if symbol in self.positions: 20 | current_qty = self.positions[symbol]['quantity'] 21 | current_cost = self.positions[symbol]['cost_basis'] * current_qty 22 | new_cost = price * quantity 23 | total_qty = current_qty + quantity 24 | 25 | if total_qty != 0: 26 | new_avg_cost = (current_cost + new_cost) / total_qty 27 | self.positions[symbol] = { 28 | 'quantity': total_qty, 29 | 'cost_basis': new_avg_cost, 30 | 'last_price': price, 31 | 'last_updated': timestamp 32 | } 33 | else: 34 | del self.positions[symbol] 35 | else: 36 | self.positions[symbol] = { 37 | 'quantity': quantity, 38 | 'cost_basis': price, 39 | 'last_price': price, 40 | 'last_updated': timestamp 41 | } 42 | 43 | cost = quantity * price 44 | self.cash -= cost 45 | 46 | self.transactions.append({ 47 | 'symbol': symbol, 48 | 'quantity': quantity, 49 | 'price': price, 50 | 'cost': cost, 51 | 'timestamp': timestamp, 52 | 'type': 'BUY' if quantity > 0 else 'SELL' 53 | }) 54 | 55 | def remove_position(self, symbol: str, quantity: int, price: float, 56 | timestamp: Optional[datetime] = None): 57 | if symbol not in self.positions: 58 | raise ValueError(f"No position found for symbol {symbol}") 59 | 60 | if timestamp is None: 61 | timestamp = datetime.now() 62 | 63 | current_qty = self.positions[symbol]['quantity'] 64 | if abs(quantity) > abs(current_qty): 65 | raise ValueError(f"Cannot sell {quantity} shares, only {current_qty} available") 66 | 67 | new_qty = current_qty - quantity 68 | proceeds = quantity * price 69 | self.cash += proceeds 70 | 71 | if new_qty == 0: 72 | del self.positions[symbol] 73 | else: 74 | self.positions[symbol]['quantity'] = new_qty 75 | self.positions[symbol]['last_price'] = price 76 | self.positions[symbol]['last_updated'] = timestamp 77 | 78 | self.transactions.append({ 79 | 'symbol': symbol, 80 | 'quantity': -quantity, 81 | 'price': price, 82 | 'cost': -proceeds, 83 | 'timestamp': timestamp, 84 | 'type': 'SELL' 85 | }) 86 | 87 | def update_prices(self, price_data: Dict[str, float], timestamp: Optional[datetime] = None): 88 | if timestamp is None: 89 | timestamp = datetime.now() 90 | 91 | for symbol, price in price_data.items(): 92 | if symbol in self.positions: 93 | self.positions[symbol]['last_price'] = price 94 | self.positions[symbol]['last_updated'] = timestamp 95 | 96 | def get_portfolio_value(self, current_prices: Optional[Dict[str, float]] = None) -> float: 97 | if current_prices: 98 | self.update_prices(current_prices) 99 | 100 | portfolio_value = self.cash 101 | for symbol, position in self.positions.items(): 102 | portfolio_value += position['quantity'] * position['last_price'] 103 | 104 | return portfolio_value 105 | 106 | def get_position_value(self, symbol: str) -> float: 107 | if symbol not in self.positions: 108 | return 0.0 109 | position = self.positions[symbol] 110 | return position['quantity'] * position['last_price'] 111 | 112 | def get_unrealized_pnl(self, symbol: str) -> float: 113 | if symbol not in self.positions: 114 | return 0.0 115 | 116 | position = self.positions[symbol] 117 | current_value = position['quantity'] * position['last_price'] 118 | cost_basis = position['quantity'] * position['cost_basis'] 119 | return current_value - cost_basis 120 | 121 | def get_total_unrealized_pnl(self) -> float: 122 | total_pnl = 0.0 123 | for symbol in self.positions: 124 | total_pnl += self.get_unrealized_pnl(symbol) 125 | return total_pnl 126 | 127 | def get_realized_pnl(self) -> float: 128 | realized_pnl = 0.0 129 | position_costs = {} 130 | 131 | for transaction in self.transactions: 132 | symbol = transaction['symbol'] 133 | quantity = transaction['quantity'] 134 | price = transaction['price'] 135 | 136 | if symbol not in position_costs: 137 | position_costs[symbol] = [] 138 | 139 | if quantity > 0: 140 | position_costs[symbol].extend([price] * quantity) 141 | else: 142 | quantity = abs(quantity) 143 | if len(position_costs[symbol]) >= quantity: 144 | sold_costs = position_costs[symbol][:quantity] 145 | position_costs[symbol] = position_costs[symbol][quantity:] 146 | realized_pnl += sum((price - cost) for cost in sold_costs) 147 | 148 | return realized_pnl 149 | 150 | def get_performance_metrics(self, current_prices: Optional[Dict[str, float]] = None) -> Dict[str, float]: 151 | current_value = self.get_portfolio_value(current_prices) 152 | total_return = current_value - self.initial_cash 153 | total_return_pct = (total_return / self.initial_cash) * 100 154 | 155 | return { 156 | 'initial_cash': self.initial_cash, 157 | 'current_cash': self.cash, 158 | 'current_portfolio_value': current_value, 159 | 'total_return': total_return, 160 | 'total_return_percentage': total_return_pct, 161 | 'unrealized_pnl': self.get_total_unrealized_pnl(), 162 | 'realized_pnl': self.get_realized_pnl(), 163 | 'number_of_positions': len(self.positions), 164 | 'number_of_transactions': len(self.transactions) 165 | } 166 | 167 | def get_positions_summary(self) -> pd.DataFrame: 168 | data = [] 169 | for symbol, position in self.positions.items(): 170 | data.append({ 171 | 'symbol': symbol, 172 | 'quantity': position['quantity'], 173 | 'cost_basis': position['cost_basis'], 174 | 'last_price': position['last_price'], 175 | 'market_value': position['quantity'] * position['last_price'], 176 | 'unrealized_pnl': self.get_unrealized_pnl(symbol), 177 | 'last_updated': position['last_updated'] 178 | }) 179 | return pd.DataFrame(data) 180 | 181 | def get_transactions_history(self) -> pd.DataFrame: 182 | return pd.DataFrame(self.transactions) 183 | 184 | def record_performance(self, timestamp: Optional[datetime] = None, 185 | current_prices: Optional[Dict[str, float]] = None): 186 | if timestamp is None: 187 | timestamp = datetime.now() 188 | 189 | metrics = self.get_performance_metrics(current_prices) 190 | metrics['timestamp'] = timestamp 191 | self.performance_history.append(metrics) 192 | 193 | def get_performance_history(self) -> pd.DataFrame: 194 | return pd.DataFrame(self.performance_history) -------------------------------------------------------------------------------- /examples/wheel_strategy_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Wheel Strategy Example 4 | 5 | This example demonstrates how to use the Wheel Options Strategy 6 | in the Gauss World Trader system. 7 | """ 8 | 9 | import sys 10 | import os 11 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 12 | 13 | from datetime import datetime, timedelta 14 | import pandas as pd 15 | import numpy as np 16 | from src.option_strategy import WheelStrategy 17 | from src.trade import Portfolio 18 | 19 | def create_mock_portfolio(): 20 | """Create a mock portfolio for testing""" 21 | portfolio = Portfolio(initial_cash=100000) 22 | 23 | # Add some mock stock positions for covered call testing 24 | portfolio.positions = { 25 | 'AAPL': {'quantity': 200, 'avg_price': 150.0, 'total_cost': 30000}, 26 | 'MSFT': {'quantity': 100, 'avg_price': 280.0, 'total_cost': 28000} 27 | } 28 | 29 | # Mock option positions 30 | portfolio.option_positions = {} 31 | 32 | return portfolio 33 | 34 | def create_mock_market_data(): 35 | """Create mock market data""" 36 | current_prices = { 37 | 'AAPL': 155.50, 38 | 'MSFT': 285.75, 39 | 'GOOGL': 138.25, 40 | 'TSLA': 185.30, 41 | 'AMZN': 145.80, 42 | 'RGTI': 12.50, 43 | 'AFRM': 45.60, 44 | 'UPST': 32.40 45 | } 46 | 47 | current_data = {} 48 | for symbol, price in current_prices.items(): 49 | current_data[symbol] = { 50 | 'open': price * 0.99, 51 | 'high': price * 1.02, 52 | 'low': price * 0.98, 53 | 'close': price, 54 | 'volume': np.random.randint(1000000, 5000000) 55 | } 56 | 57 | # Create some historical data 58 | historical_data = {} 59 | for symbol, price in current_prices.items(): 60 | dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D') 61 | prices = price * (1 + np.random.randn(len(dates)).cumsum() * 0.01) 62 | 63 | historical_data[symbol] = pd.DataFrame({ 64 | 'open': prices * np.random.uniform(0.99, 1.01, len(dates)), 65 | 'high': prices * np.random.uniform(1.00, 1.03, len(dates)), 66 | 'low': prices * np.random.uniform(0.97, 1.00, len(dates)), 67 | 'close': prices, 68 | 'volume': np.random.randint(500000, 2000000, len(dates)) 69 | }, index=dates) 70 | 71 | return current_prices, current_data, historical_data 72 | 73 | def test_wheel_strategy(): 74 | """Test the wheel strategy with mock data""" 75 | 76 | print("🎯 Wheel Strategy Test") 77 | print("=" * 50) 78 | 79 | # Initialize the strategy 80 | wheel_params = { 81 | 'max_risk': 50000, # Reduce for testing 82 | 'position_size_pct': 0.05, # 5% position sizes 83 | 'max_positions': 5, # Max 5 positions 84 | 'put_delta_min': 0.20, # 20% assignment probability 85 | 'put_delta_max': 0.35, # 35% assignment probability 86 | 'min_yield': 0.03, # 3% minimum yield 87 | 'dte_min': 7, # 7 days minimum 88 | 'dte_max': 35, # 35 days maximum 89 | } 90 | 91 | strategy = WheelStrategy(wheel_params) 92 | 93 | # Create mock data 94 | portfolio = create_mock_portfolio() 95 | current_prices, current_data, historical_data = create_mock_market_data() 96 | 97 | print(f"📊 Strategy Info:") 98 | info = strategy.get_strategy_info() 99 | print(f" Name: {info['name']}") 100 | print(f" Type: {info['type']}") 101 | print(f" Risk Level: {info['risk_level']}") 102 | print(f" Timeframe: {info['timeframe']}") 103 | print(f" Watchlist Symbols: {info['watchlist_symbols']}") 104 | 105 | print(f"\n💰 Portfolio Status:") 106 | print(f" Cash: ${portfolio.cash:,.2f}") 107 | print(f" Stock Positions: {len(portfolio.positions)}") 108 | print(f" Option Positions: {len(getattr(portfolio, 'option_positions', {}))}") 109 | 110 | # Generate signals 111 | print(f"\n🔄 Generating Wheel Strategy Signals...") 112 | current_date = datetime.now() 113 | 114 | signals = strategy.generate_signals( 115 | current_date=current_date, 116 | current_prices=current_prices, 117 | current_data=current_data, 118 | historical_data=historical_data, 119 | portfolio=portfolio 120 | ) 121 | 122 | print(f"\n📈 Generated {len(signals)} signals:") 123 | 124 | if signals: 125 | for i, signal in enumerate(signals, 1): 126 | print(f"\n Signal {i}:") 127 | print(f" Symbol: {signal['symbol']}") 128 | print(f" Underlying: {signal.get('underlying_symbol', 'N/A')}") 129 | print(f" Action: {signal['action']}") 130 | print(f" Type: {signal.get('option_type', 'stock')}") 131 | print(f" Quantity: {signal['quantity']}") 132 | print(f" Strategy Stage: {signal.get('strategy_stage', 'N/A')}") 133 | print(f" Reason: {signal.get('reason', 'N/A')}") 134 | print(f" Confidence: {signal.get('confidence', 0):.1%}") 135 | 136 | if 'strike_price' in signal: 137 | print(f" Strike Price: ${signal['strike_price']:.2f}") 138 | if 'premium' in signal: 139 | print(f" Premium: ${signal['premium']:.2f}") 140 | if 'yield' in signal: 141 | print(f" Yield: {signal['yield']:.2f}%") 142 | if 'score' in signal: 143 | print(f" Score: {signal['score']:.4f}") 144 | else: 145 | print(" No signals generated (normal in current market conditions)") 146 | 147 | print(f"\n📊 Strategy Parameters:") 148 | params = strategy.parameters 149 | print(f" Max Risk: ${params['max_risk']:,}") 150 | print(f" Position Size: {params['position_size_pct']:.1%}") 151 | print(f" Put Delta Range: {params['put_delta_min']:.2f} - {params['put_delta_max']:.2f}") 152 | print(f" Min Yield: {params['min_yield']:.1%}") 153 | print(f" DTE Range: {params['dte_min']} - {params['dte_max']} days") 154 | 155 | return strategy, signals 156 | 157 | def demonstrate_strategy_cycle(): 158 | """Demonstrate the complete wheel strategy cycle""" 159 | 160 | print(f"\n🔄 Wheel Strategy Cycle Demonstration") 161 | print("=" * 50) 162 | 163 | print(""" 164 | The Wheel Strategy operates in a systematic cycle: 165 | 166 | 1. 🎯 CASH-SECURED PUTS 167 | - Sell put options on stocks you want to own 168 | - Collect premium income 169 | - Wait for expiration or assignment 170 | 171 | 2. 📦 ASSIGNMENT (if put expires ITM) 172 | - Receive 100 shares per contract 173 | - Pay the strike price 174 | - Now own the underlying stock 175 | 176 | 3. 📞 COVERED CALLS 177 | - Sell call options on owned shares 178 | - Collect additional premium 179 | - Wait for expiration or assignment 180 | 181 | 4. 🔄 CALL AWAY (if call expires ITM) 182 | - Shares are sold at strike price 183 | - Collect the call premium 184 | - Return to step 1 with cash 185 | 186 | 💡 Key Benefits: 187 | - Generate income at every step 188 | - Potentially acquire stocks at lower prices 189 | - Systematic approach to options trading 190 | - Built-in risk management 191 | 192 | ⚠️ Key Risks: 193 | - Assignment at unfavorable prices 194 | - Opportunity cost if stock rises significantly 195 | - Requires active management 196 | - Capital intensive (cash-secured puts) 197 | """) 198 | 199 | if __name__ == '__main__': 200 | try: 201 | # Test the strategy 202 | strategy, signals = test_wheel_strategy() 203 | 204 | # Demonstrate the cycle 205 | demonstrate_strategy_cycle() 206 | 207 | print(f"\n✅ Wheel Strategy Test Complete!") 208 | print(f"\n📚 Next Steps:") 209 | print(f" 1. Review generated signals above") 210 | print(f" 2. Integrate with live Alpaca API for real option data") 211 | print(f" 3. Run backtests with historical data") 212 | print(f" 4. Paper trade to validate strategy") 213 | print(f" 5. Deploy with real capital (start small!)") 214 | 215 | except Exception as e: 216 | print(f"❌ Error running wheel strategy test: {e}") 217 | import traceback 218 | traceback.print_exc() -------------------------------------------------------------------------------- /examples/simple_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Simple working example of the trading system 4 | Tests core functionality without complex dependencies 5 | """ 6 | 7 | import sys 8 | from datetime import datetime, timedelta 9 | 10 | # Add project to path 11 | sys.path.insert(0, '.') 12 | 13 | def test_config(): 14 | """Test configuration loading""" 15 | print("🔧 Testing configuration...") 16 | try: 17 | from config import Config 18 | 19 | # Test basic config 20 | alpaca_valid = Config.validate_alpaca_config() 21 | finnhub_valid = Config.validate_finnhub_config() 22 | fred_valid = Config.validate_fred_config() 23 | 24 | print(f" Alpaca API: {'✅ Valid' if alpaca_valid else '❌ Missing/Invalid'}") 25 | print(f" Finnhub API: {'✅ Valid' if finnhub_valid else '❌ Missing/Invalid'}") 26 | print(f" FRED API: {'✅ Valid' if fred_valid else '❌ Missing/Invalid'}") 27 | 28 | return alpaca_valid 29 | 30 | except Exception as e: 31 | print(f" ❌ Config error: {e}") 32 | return False 33 | 34 | def test_data_provider(use_alpaca=True): 35 | """Test data provider""" 36 | print("\n📊 Testing data provider...") 37 | 38 | try: 39 | if use_alpaca: 40 | from src.data import AlpacaDataProvider 41 | provider = AlpacaDataProvider() 42 | else: 43 | # Mock data provider for testing 44 | print(" Using mock data (no API keys needed)") 45 | import pandas as pd 46 | import numpy as np 47 | 48 | class MockProvider: 49 | def get_bars(self, symbol, timeframe, start_date, end_date): 50 | dates = pd.date_range(start=start_date, end=end_date, freq='D') 51 | np.random.seed(42) # For reproducible results 52 | 53 | data = { 54 | 'open': 100 + np.random.randn(len(dates)).cumsum(), 55 | 'high': 102 + np.random.randn(len(dates)).cumsum(), 56 | 'low': 98 + np.random.randn(len(dates)).cumsum(), 57 | 'close': 101 + np.random.randn(len(dates)).cumsum(), 58 | 'volume': np.random.randint(10000, 100000, len(dates)) 59 | } 60 | 61 | df = pd.DataFrame(data, index=dates) 62 | # Ensure high >= max(open, close) and low <= min(open, close) 63 | df['high'] = df[['open', 'close', 'high']].max(axis=1) 64 | df['low'] = df[['open', 'close', 'low']].min(axis=1) 65 | return df 66 | 67 | provider = MockProvider() 68 | 69 | # Test data fetching 70 | symbol = 'AAPL' 71 | end_date = datetime.now() - timedelta(days=1) # Avoid weekend issues 72 | start_date = end_date - timedelta(days=30) 73 | 74 | print(f" Fetching {symbol} data from {start_date.date()} to {end_date.date()}") 75 | 76 | data = provider.get_bars(symbol, '1Day', start_date) 77 | 78 | if not data.empty: 79 | print(f" ✅ Retrieved {len(data)} bars") 80 | print(f" 📈 Latest price: ${data['close'].iloc[-1]:.2f}") 81 | print(f" 📊 Price range: ${data['low'].min():.2f} - ${data['high'].max():.2f}") 82 | return data 83 | else: 84 | print(" ❌ No data retrieved") 85 | return None 86 | 87 | except Exception as e: 88 | print(f" ❌ Data provider error: {e}") 89 | print(" 💡 Trying with mock data instead...") 90 | return test_data_provider(use_alpaca=False) 91 | 92 | def test_strategy(data): 93 | """Test trading strategy""" 94 | print("\n🧠 Testing momentum strategy...") 95 | 96 | try: 97 | from src.stock_strategy import MomentumStrategy 98 | from src.trade import Portfolio 99 | 100 | strategy = MomentumStrategy() 101 | portfolio = Portfolio() 102 | 103 | # Prepare data 104 | symbol = 'AAPL' 105 | current_prices = {symbol: data['close'].iloc[-1]} 106 | historical_data = {symbol: data} 107 | current_data = { 108 | symbol: { 109 | 'open': data['open'].iloc[-1], 110 | 'high': data['high'].iloc[-1], 111 | 'low': data['low'].iloc[-1], 112 | 'close': data['close'].iloc[-1], 113 | 'volume': data['volume'].iloc[-1] 114 | } 115 | } 116 | 117 | # Generate signals 118 | signals = strategy.generate_signals( 119 | current_date=datetime.now(), 120 | current_prices=current_prices, 121 | current_data=current_data, 122 | historical_data=historical_data, 123 | portfolio=portfolio 124 | ) 125 | 126 | if signals: 127 | print(f" ✅ Generated {len(signals)} signals:") 128 | for signal in signals: 129 | print(f" 📊 {signal['symbol']}: {signal['action'].upper()} {signal['quantity']} shares") 130 | print(f" Reason: {signal.get('reason', 'N/A')}") 131 | print(f" Confidence: {signal.get('confidence', 0):.1%}") 132 | else: 133 | print(" 📭 No trading signals generated (normal for current market conditions)") 134 | 135 | return signals 136 | 137 | except Exception as e: 138 | print(f" ❌ Strategy error: {e}") 139 | return None 140 | 141 | def test_backtesting(data): 142 | """Test simple backtesting""" 143 | print("\n🔄 Testing backtesting framework...") 144 | 145 | try: 146 | from src.trade import Backtester 147 | from src.stock_strategy import MomentumStrategy 148 | 149 | # Create backtester 150 | backtester = Backtester(initial_cash=10000, commission=0.01) 151 | backtester.add_data('AAPL', data) 152 | 153 | # Create strategy 154 | strategy = MomentumStrategy() 155 | 156 | def strategy_func(current_date, current_prices, current_data, historical_data, portfolio): 157 | return strategy.generate_signals( 158 | current_date, current_prices, current_data, historical_data, portfolio 159 | ) 160 | 161 | # Run backtest on subset of data 162 | start_date = data.index[20] # Skip first 20 days for indicators 163 | end_date = data.index[-1] 164 | 165 | print(f" Running backtest from {start_date.date()} to {end_date.date()}") 166 | 167 | results = backtester.run_backtest( 168 | strategy_func, 169 | start_date=start_date, 170 | end_date=end_date, 171 | symbols=['AAPL'] 172 | ) 173 | 174 | if results: 175 | print(" ✅ Backtest completed successfully!") 176 | print(f" 💰 Initial Value: ${results['initial_value']:,.2f}") 177 | print(f" 💰 Final Value: ${results['final_value']:,.2f}") 178 | print(f" 📈 Total Return: {results['total_return_percentage']:.2f}%") 179 | print(f" 📊 Total Trades: {results['total_trades']}") 180 | print(f" 🎯 Win Rate: {results['win_rate']:.1f}%") 181 | else: 182 | print(" ❌ Backtest failed") 183 | 184 | return results 185 | 186 | except Exception as e: 187 | print(f" ❌ Backtesting error: {e}") 188 | return None 189 | 190 | def main(): 191 | """Run all tests""" 192 | print("🚀 Trading System Test Suite") 193 | print("=" * 50) 194 | 195 | # Test configuration 196 | has_api_keys = test_config() 197 | 198 | # Test data provider 199 | data = test_data_provider(use_alpaca=has_api_keys) 200 | 201 | if data is not None: 202 | # Test strategy 203 | test_strategy(data) 204 | 205 | # Test backtesting 206 | test_backtesting(data) 207 | 208 | print("\n" + "=" * 50) 209 | print("🎉 Test suite completed!") 210 | print("\n💡 To run individual components:") 211 | print(" python simple_example.py") 212 | print(" python examples/momentum_backtest_example.py # (needs API keys)") 213 | 214 | if __name__ == '__main__': 215 | main() -------------------------------------------------------------------------------- /wheel_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Wheel Strategy Command Line Interface 4 | 5 | Simple CLI for running and managing the Wheel Options Strategy. 6 | """ 7 | 8 | import argparse 9 | import sys 10 | import json 11 | from datetime import datetime, timedelta 12 | from pathlib import Path 13 | 14 | # Add project root to path 15 | project_root = Path(__file__).parent 16 | sys.path.insert(0, str(project_root)) 17 | 18 | from src.option_strategy import WheelStrategy 19 | from src.trade import Portfolio 20 | import pandas as pd 21 | import numpy as np 22 | 23 | 24 | def create_sample_data(): 25 | """Create sample market data for testing""" 26 | current_prices = { 27 | 'AAPL': 155.50, 28 | 'MSFT': 285.75, 29 | 'GOOGL': 138.25, 30 | 'TSLA': 185.30, 31 | 'AMZN': 145.80, 32 | 'RGTI': 12.50, 33 | 'AFRM': 45.60, 34 | 'UPST': 32.40 35 | } 36 | 37 | current_data = {} 38 | for symbol, price in current_prices.items(): 39 | current_data[symbol] = { 40 | 'open': price * 0.99, 41 | 'high': price * 1.02, 42 | 'low': price * 0.98, 43 | 'close': price, 44 | 'volume': np.random.randint(1000000, 5000000) 45 | } 46 | 47 | return current_prices, current_data 48 | 49 | 50 | def run_wheel_strategy(config_file=None, verbose=False): 51 | """Run the wheel strategy with given configuration""" 52 | 53 | print("🎯 Gauss World Trader - Wheel Options Strategy") 54 | print("=" * 50) 55 | 56 | # Load configuration 57 | if config_file: 58 | config_path = Path(config_file) 59 | else: 60 | # Default to config/wheel_config.json 61 | config_path = Path("config/wheel_config.json") 62 | 63 | if config_path.exists(): 64 | with open(config_path, 'r') as f: 65 | config = json.load(f) 66 | print(f"📄 Loaded configuration from {config_path}") 67 | else: 68 | # Default configuration 69 | config = { 70 | 'max_risk': 50000, 71 | 'position_size_pct': 0.08, 72 | 'put_delta_min': 0.20, 73 | 'put_delta_max': 0.35, 74 | 'min_yield': 0.04, 75 | 'dte_min': 14, 76 | 'dte_max': 35, 77 | 'max_positions': 5 78 | } 79 | print("📄 Using default configuration") 80 | 81 | if verbose: 82 | print(f"📊 Configuration: {json.dumps(config, indent=2)}") 83 | 84 | # Initialize strategy 85 | print("\n🔧 Initializing Wheel Strategy...") 86 | strategy = WheelStrategy(config) 87 | 88 | # Create mock portfolio and data 89 | portfolio = Portfolio(initial_cash=100000) 90 | current_prices, current_data = create_sample_data() 91 | 92 | print(f"💰 Portfolio: ${portfolio.cash:,.2f} cash") 93 | print(f"📈 Market data: {len(current_prices)} symbols") 94 | 95 | # Generate signals 96 | print("\n🔄 Generating wheel strategy signals...") 97 | signals = strategy.generate_signals( 98 | current_date=datetime.now(), 99 | current_prices=current_prices, 100 | current_data=current_data, 101 | historical_data={}, 102 | portfolio=portfolio 103 | ) 104 | 105 | # Display results 106 | print(f"\n📊 Generated {len(signals)} signals:") 107 | 108 | if signals: 109 | for i, signal in enumerate(signals, 1): 110 | print(f"\n [{i}] {signal['symbol']}") 111 | print(f" Action: {signal['action']}") 112 | print(f" Type: {signal.get('option_type', 'N/A')}") 113 | print(f" Quantity: {signal['quantity']}") 114 | print(f" Stage: {signal.get('strategy_stage', 'N/A')}") 115 | print(f" Reason: {signal.get('reason', 'N/A')}") 116 | 117 | if 'strike_price' in signal: 118 | print(f" Strike: ${signal['strike_price']:.2f}") 119 | if 'premium' in signal: 120 | print(f" Premium: ${signal['premium']:.2f}") 121 | if 'yield' in signal: 122 | print(f" Yield: {signal['yield']:.2f}%") 123 | if 'confidence' in signal: 124 | print(f" Confidence: {signal['confidence']:.1%}") 125 | else: 126 | print(" No signals generated (normal in current conditions)") 127 | 128 | # Strategy info 129 | if verbose: 130 | print(f"\n📋 Strategy Information:") 131 | info = strategy.get_strategy_info() 132 | print(f" Name: {info['name']}") 133 | print(f" Type: {info['type']}") 134 | print(f" Risk Level: {info['risk_level']}") 135 | print(f" Watchlist Symbols: {info['watchlist_symbols']}") 136 | 137 | return signals 138 | 139 | 140 | def generate_config_template(): 141 | """Generate a configuration template file""" 142 | config_template = { 143 | "max_risk": 80000, 144 | "position_size_pct": 0.08, 145 | "put_delta_min": 0.15, 146 | "put_delta_max": 0.30, 147 | "call_delta_min": 0.15, 148 | "call_delta_max": 0.30, 149 | "min_yield": 0.04, 150 | "max_yield": 1.00, 151 | "dte_min": 7, 152 | "dte_max": 45, 153 | "preferred_dte": 21, 154 | "min_open_interest": 100, 155 | "min_daily_volume": 50, 156 | "min_score": 0.05, 157 | "max_options_per_underlying": 1, 158 | "assignment_tolerance": 0.80, 159 | "profit_target": 0.50, 160 | "management_dte": 7, 161 | "max_positions": 10, 162 | "min_stock_price": 10.0, 163 | "max_stock_price": 500.0 164 | } 165 | 166 | # Ensure config directory exists 167 | config_dir = Path("config") 168 | config_dir.mkdir(exist_ok=True) 169 | 170 | config_file = config_dir / "wheel_config.json" 171 | with open(config_file, 'w') as f: 172 | json.dump(config_template, f, indent=2) 173 | 174 | print(f"✅ Configuration template created: {config_file}") 175 | print("Edit this file to customize your wheel strategy parameters.") 176 | 177 | 178 | def show_strategy_info(): 179 | """Show detailed strategy information""" 180 | print("🎯 Wheel Options Strategy Information") 181 | print("=" * 50) 182 | 183 | print(""" 184 | The Wheel Strategy is a systematic options trading approach that: 185 | 186 | 1. 🎯 SELLS CASH-SECURED PUTS 187 | - Target stocks you want to own 188 | - Collect premium income 189 | - Be willing to be assigned 190 | 191 | 2. 📦 MANAGES ASSIGNMENT 192 | - Purchase shares at strike price 193 | - Move to covered call phase 194 | - Maintain disciplined approach 195 | 196 | 3. 📞 SELLS COVERED CALLS 197 | - Generate additional income 198 | - Target profitable exit prices 199 | - Complete the wheel cycle 200 | 201 | KEY BENEFITS: 202 | ✅ Systematic income generation 203 | ✅ Disciplined stock acquisition 204 | ✅ Built-in risk management 205 | ✅ Suitable for stable stocks 206 | 207 | KEY RISKS: 208 | ⚠️ Assignment at unfavorable prices 209 | ⚠️ Opportunity cost in bull markets 210 | ⚠️ Requires significant capital 211 | ⚠️ Active management needed 212 | 213 | IDEAL CONDITIONS: 214 | 📈 Moderate volatility 215 | 📈 Stable, quality stocks 216 | 📈 Neutral to slightly bullish outlook 217 | 📈 Sufficient capital for assignment 218 | """) 219 | 220 | 221 | def main(): 222 | """Main CLI function""" 223 | parser = argparse.ArgumentParser( 224 | description="Wheel Options Strategy CLI", 225 | formatter_class=argparse.RawDescriptionHelpFormatter 226 | ) 227 | 228 | parser.add_argument( 229 | '--config', '-c', 230 | help='Configuration file path (default: config/wheel_config.json)', 231 | type=str 232 | ) 233 | 234 | parser.add_argument( 235 | '--verbose', '-v', 236 | help='Verbose output', 237 | action='store_true' 238 | ) 239 | 240 | parser.add_argument( 241 | '--generate-config', 242 | help='Generate configuration template', 243 | action='store_true' 244 | ) 245 | 246 | parser.add_argument( 247 | '--info', 248 | help='Show strategy information', 249 | action='store_true' 250 | ) 251 | 252 | parser.add_argument( 253 | '--run', 254 | help='Run the wheel strategy', 255 | action='store_true' 256 | ) 257 | 258 | args = parser.parse_args() 259 | 260 | try: 261 | if args.generate_config: 262 | generate_config_template() 263 | elif args.info: 264 | show_strategy_info() 265 | elif args.run: 266 | run_wheel_strategy(args.config, args.verbose) 267 | else: 268 | # Default action - run the strategy 269 | run_wheel_strategy(args.config, args.verbose) 270 | 271 | except KeyboardInterrupt: 272 | print("\n\n🛑 Interrupted by user") 273 | sys.exit(1) 274 | except Exception as e: 275 | print(f"\n❌ Error: {e}") 276 | if args.verbose: 277 | import traceback 278 | traceback.print_exc() 279 | sys.exit(1) 280 | 281 | 282 | if __name__ == '__main__': 283 | main() -------------------------------------------------------------------------------- /src/stock_strategy/base_strategy.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | import pandas as pd 5 | from datetime import datetime 6 | import logging 7 | from typing import TYPE_CHECKING, Dict, List, Any 8 | 9 | if TYPE_CHECKING: 10 | from collections.abc import Sequence 11 | 12 | class BaseStrategy(ABC): 13 | def __init__(self, parameters: Dict[str, Any] = None) -> None: 14 | self.parameters = parameters or {} 15 | self.logger = logging.getLogger(self.__class__.__name__) 16 | self.name = self.__class__.__name__ # Add name attribute 17 | 18 | # Strategy state 19 | self.positions: Dict[str, Any] = {} 20 | self.signals: List[Dict[str, Any]] = [] 21 | self.performance_metrics: Dict[str, Any] = {} 22 | 23 | @abstractmethod 24 | def generate_signals(self, current_date: datetime, current_prices: Dict[str, float], 25 | current_data: Dict[str, Any], 26 | historical_data: Dict[str, pd.DataFrame], 27 | portfolio: Any = None) -> List[Dict[str, Any]]: 28 | pass 29 | 30 | @abstractmethod 31 | def calculate_position_size(self, symbol: str, price: float, 32 | portfolio_value: float, volatility: float = None) -> int: 33 | pass 34 | 35 | def validate_signal(self, signal: Dict[str, Any]) -> bool: 36 | required_fields = ['symbol', 'action', 'quantity'] 37 | 38 | if not all(field in signal for field in required_fields): 39 | self.logger.warning(f"Signal missing required fields: {signal}") 40 | return False 41 | 42 | if signal['action'].upper() not in ['BUY', 'SELL']: 43 | self.logger.warning(f"Invalid action in signal: {signal['action']}") 44 | return False 45 | 46 | if not isinstance(signal['quantity'], (int, float)) or signal['quantity'] <= 0: 47 | self.logger.warning(f"Invalid quantity in signal: {signal['quantity']}") 48 | return False 49 | 50 | return True 51 | 52 | def update_position(self, symbol: str, quantity: int, price: float, 53 | action: str, timestamp: datetime): 54 | if symbol not in self.positions: 55 | self.positions[symbol] = { 56 | 'quantity': 0, 57 | 'avg_price': 0, 58 | 'total_cost': 0, 59 | 'last_updated': timestamp 60 | } 61 | 62 | position = self.positions[symbol] 63 | 64 | if action.upper() == 'BUY': 65 | new_quantity = position['quantity'] + quantity 66 | new_total_cost = position['total_cost'] + (quantity * price) 67 | 68 | if new_quantity > 0: 69 | new_avg_price = new_total_cost / new_quantity 70 | self.positions[symbol].update({ 71 | 'quantity': new_quantity, 72 | 'avg_price': new_avg_price, 73 | 'total_cost': new_total_cost, 74 | 'last_updated': timestamp 75 | }) 76 | else: 77 | self.positions[symbol].update({ 78 | 'quantity': new_quantity, 79 | 'avg_price': price, 80 | 'total_cost': new_total_cost, 81 | 'last_updated': timestamp 82 | }) 83 | 84 | elif action.upper() == 'SELL': 85 | new_quantity = position['quantity'] - quantity 86 | cost_per_share = position['avg_price'] 87 | cost_reduction = quantity * cost_per_share 88 | 89 | self.positions[symbol].update({ 90 | 'quantity': new_quantity, 91 | 'total_cost': position['total_cost'] - cost_reduction, 92 | 'last_updated': timestamp 93 | }) 94 | 95 | if new_quantity == 0: 96 | self.positions[symbol]['avg_price'] = 0 97 | self.positions[symbol]['total_cost'] = 0 98 | 99 | def get_technical_indicators(self, data: pd.DataFrame) -> Dict[str, pd.Series]: 100 | indicators = {} 101 | 102 | # Simple Moving Averages 103 | if len(data) >= 20: 104 | indicators['sma_20'] = data['close'].rolling(window=20).mean() 105 | if len(data) >= 50: 106 | indicators['sma_50'] = data['close'].rolling(window=50).mean() 107 | if len(data) >= 200: 108 | indicators['sma_200'] = data['close'].rolling(window=200).mean() 109 | 110 | # Exponential Moving Averages 111 | if len(data) >= 12: 112 | indicators['ema_12'] = data['close'].ewm(span=12).mean() 113 | if len(data) >= 26: 114 | indicators['ema_26'] = data['close'].ewm(span=26).mean() 115 | 116 | # RSI 117 | if len(data) >= 14: 118 | indicators['rsi'] = self._calculate_rsi(data['close'], 14) 119 | 120 | # MACD 121 | if len(data) >= 26: 122 | indicators['macd'], indicators['macd_signal'], indicators['macd_histogram'] = self._calculate_macd(data['close']) 123 | 124 | # Bollinger Bands 125 | if len(data) >= 20: 126 | bb_upper, bb_middle, bb_lower = self._calculate_bollinger_bands(data['close'], 20, 2) 127 | indicators['bb_upper'] = bb_upper 128 | indicators['bb_middle'] = bb_middle 129 | indicators['bb_lower'] = bb_lower 130 | 131 | # Volume indicators 132 | if 'volume' in data.columns and len(data) >= 20: 133 | indicators['volume_sma'] = data['volume'].rolling(window=20).mean() 134 | 135 | return indicators 136 | 137 | def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series: 138 | delta = prices.diff() 139 | gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() 140 | loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() 141 | rs = gain / loss 142 | return 100 - (100 / (1 + rs)) 143 | 144 | def _calculate_macd(self, prices: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9): 145 | ema_fast = prices.ewm(span=fast).mean() 146 | ema_slow = prices.ewm(span=slow).mean() 147 | macd = ema_fast - ema_slow 148 | macd_signal = macd.ewm(span=signal).mean() 149 | macd_histogram = macd - macd_signal 150 | return macd, macd_signal, macd_histogram 151 | 152 | def _calculate_bollinger_bands(self, prices: pd.Series, period: int = 20, std_dev: int = 2): 153 | sma = prices.rolling(window=period).mean() 154 | std = prices.rolling(window=period).std() 155 | upper = sma + (std * std_dev) 156 | lower = sma - (std * std_dev) 157 | return upper, sma, lower 158 | 159 | def risk_management_check(self, signal: Dict[str, Any], portfolio, 160 | max_position_size: float = 0.1) -> Dict[str, Any]: 161 | symbol = signal['symbol'] 162 | quantity = signal['quantity'] 163 | action = signal['action'].upper() 164 | 165 | if action == 'BUY': 166 | portfolio_value = portfolio.get_portfolio_value() 167 | position_value = quantity * signal.get('price', 0) 168 | position_ratio = position_value / portfolio_value 169 | 170 | if position_ratio > max_position_size: 171 | new_quantity = int((max_position_size * portfolio_value) / signal.get('price', 1)) 172 | self.logger.info(f"Reducing position size for {symbol} from {quantity} to {new_quantity}") 173 | signal['quantity'] = max(1, new_quantity) 174 | 175 | elif action == 'SELL': 176 | current_position = portfolio.positions.get(symbol, {}).get('quantity', 0) 177 | if quantity > current_position: 178 | self.logger.info(f"Reducing sell quantity for {symbol} from {quantity} to {current_position}") 179 | signal['quantity'] = current_position 180 | 181 | return signal 182 | 183 | def get_strategy_info(self) -> Dict[str, Any]: 184 | return { 185 | 'name': self.__class__.__name__, 186 | 'type': 'Unknown', 187 | 'timeframe': 'Unknown', 188 | 'risk_level': 'Unknown', 189 | 'expected_trades_per_day': 'Unknown', 190 | 'holding_period': 'Unknown', 191 | 'description': 'No description', 192 | 'parameters': self.parameters, 193 | 'current_positions': self.positions, 194 | 'total_signals_generated': len(self.signals), 195 | 'performance_metrics': self.performance_metrics 196 | } 197 | 198 | def log_signal(self, signal: Dict[str, Any]): 199 | self.signals.append({ 200 | **signal, 201 | 'timestamp': datetime.now(), 202 | 'strategy': self.name 203 | }) 204 | self.logger.info(f"Generated signal: {signal}") 205 | 206 | def reset_strategy_state(self): 207 | self.positions.clear() 208 | self.signals.clear() 209 | self.performance_metrics.clear() 210 | self.logger.info(f"Strategy {self.name} state reset") -------------------------------------------------------------------------------- /docs/PROJECT_STRUCTURE.md: -------------------------------------------------------------------------------- 1 | # Gauss World Trader - Project Structure 2 | 3 | ## Overview 4 | 5 | This document describes the organized project structure following clean architecture principles with high cohesion and low coupling. 6 | 7 | ## Directory Structure 8 | 9 | ``` 10 | GaussWorldTrader/ 11 | ├── main.py # Main entry point (CLI interface selection) 12 | ├── dashboard.py # Dashboard launcher (Web UI interface) 13 | ├── CLAUDE.md # Code style guidelines 14 | ├── README.md # Project documentation 15 | ├── pyproject.toml # Python project configuration 16 | ├── requirements.txt # Dependencies 17 | ├── watchlist.json # Default watchlist configuration 18 | │ 19 | ├── config/ # Configuration management 20 | │ ├── __init__.py 21 | │ └── config.py # Modern Python 3.12+ configuration with legacy compatibility 22 | │ 23 | ├── src/ # Main source code 24 | │ ├── __init__.py 25 | │ │ 26 | │ ├── account/ # Account and portfolio management 27 | │ │ ├── __init__.py 28 | │ │ ├── account_config.py # Account configuration 29 | │ │ ├── account_manager.py # Main account management 30 | │ │ ├── order_manager.py # Order handling 31 | │ │ ├── portfolio_tracker.py # Portfolio tracking 32 | │ │ └── position_manager.py # Position management 33 | │ │ 34 | │ ├── agent/ # AI/ML agents and analysis 35 | │ │ ├── __init__.py 36 | │ │ ├── agent_manager.py # Agent coordination 37 | │ │ ├── data_sources.py # [REMOVED] Data source management 38 | │ │ ├── fundamental_analyzer.py # Fundamental analysis 39 | │ │ └── llm_providers.py # LLM integration 40 | │ │ 41 | │ ├── analysis/ # Financial analysis tools 42 | │ │ ├── __init__.py 43 | │ │ ├── financial_metrics.py # Financial calculations 44 | │ │ ├── technical_analysis.py # Technical indicators 45 | │ │ └── performance_analyzer.py # Performance metrics & analysis 46 | │ │ 47 | │ ├── data/ # Data providers and handlers 48 | │ │ ├── __init__.py 49 | │ │ ├── alpaca_provider.py # Unified data provider (stocks, options, crypto) 50 | │ │ ├── macro_provider.py # Macroeconomic data 51 | │ │ └── news_provider.py # News and sentiment data 52 | │ │ 53 | │ ├── strategy/ # Trading strategies 54 | │ │ ├── __init__.py 55 | │ │ ├── base_strategy.py # Base strategy interface 56 | │ │ ├── strategy_selector.py # Strategy selection system 57 | │ │ ├── momentum_strategy.py # Momentum-based trading 58 | │ │ ├── arbitrage_strategy.py # Arbitrage opportunities 59 | │ │ ├── scalping_strategy.py # High-frequency scalping 60 | │ │ ├── trend_following_strategy.py # Trend following 61 | │ │ ├── value_strategy.py # Value investing 62 | │ │ ├── deep_learning_strategy.py # Neural networks 63 | │ │ ├── gaussian_process_strategy.py # Gaussian processes 64 | │ │ └── xgboost_strategy.py # XGBoost ML strategy 65 | │ │ 66 | │ ├── trade/ # Trading execution and backtesting 67 | │ │ ├── __init__.py 68 | │ │ ├── backtester.py # Backtesting engine 69 | │ │ ├── portfolio.py # Portfolio management 70 | │ │ ├── trading_engine.py # Basic trading engine 71 | │ │ └── optimized_trading_engine.py # High-performance engine 72 | │ │ 73 | │ ├── ui/ # User interfaces 74 | │ │ ├── __init__.py 75 | │ │ ├── core_cli.py # Base CLI abstraction (shared functionality) 76 | │ │ ├── dashboard.py # Dashboard launcher 77 | │ │ ├── simple_dashboard.py # Basic Streamlit dashboard 78 | │ │ ├── advanced_dashboard.py # Advanced Streamlit dashboard 79 | │ │ ├── modern_dashboard.py # Unified modern dashboard 80 | │ │ ├── simple_cli.py # Basic CLI interface (uses core_cli) 81 | │ │ ├── modern_cli.py # Modern CLI with Rich (uses core_cli, primary) 82 | │ │ └── portfolio_commands.py # Portfolio CLI commands 83 | │ │ 84 | │ └── utils/ # Shared utilities 85 | │ ├── __init__.py 86 | │ ├── dashboard_utils.py # Shared dashboard functions 87 | │ ├── error_handling.py # Error management 88 | │ ├── logger.py # Logging configuration 89 | │ ├── timezone_utils.py # Timezone handling 90 | │ ├── validators.py # Data validation 91 | │ └── watchlist_manager.py # Watchlist management 92 | │ 93 | ├── examples/ # Example scripts and tutorials 94 | │ ├── README.md 95 | │ ├── simple_example.py # Basic usage example 96 | │ ├── momentum_backtest_example.py # Momentum strategy demo 97 | │ ├── advanced_strategies_example.py # All strategies demo 98 | │ └── run_backtest_example.py # CLI backtest example 99 | │ 100 | ├── tests/ # Test suite 101 | │ └── test_dashboard.py # Dashboard tests 102 | │ 103 | ├── results/ # Generated results and outputs 104 | │ ├── backtest_results_*.png # Chart outputs 105 | │ └── transactions_*.csv # Transaction logs 106 | │ 107 | └── docs/ # Documentation 108 | └── PROJECT_STRUCTURE.md # This file 109 | ``` 110 | 111 | ## Architecture Principles 112 | 113 | ### 1. High Cohesion, Low Coupling 114 | - **Modules**: Each directory contains related functionality 115 | - **Interfaces**: Clear boundaries between components 116 | - **Dependencies**: Minimal cross-module dependencies 117 | 118 | ### 2. Separation of Concerns 119 | - **Data Layer**: `src/data/` - External data sources 120 | - **Business Logic**: `src/strategy/`, `src/trade/` - Core trading logic 121 | - **Presentation**: `src/ui/` - User interfaces 122 | - **Infrastructure**: `src/utils/`, `config/` - Supporting services 123 | 124 | ### 3. Code Reuse and DRY 125 | - **Shared Utilities**: Common functions in `src/utils/` 126 | - **Base Classes**: Abstract interfaces in `src/strategy/base_strategy.py` 127 | - **Configuration**: Centralized in `config/` 128 | 129 | ## Module Responsibilities 130 | 131 | ### Core Trading Modules 132 | - **`src/strategy/`**: Trading algorithms and decision-making logic 133 | - **`src/trade/`**: Order execution, backtesting, and portfolio management 134 | - **`src/data/`**: Market data acquisition and processing 135 | 136 | ### Supporting Modules 137 | - **`src/account/`**: Account state management and position tracking 138 | - **`src/analysis/`**: Financial calculations, technical indicators, and performance analysis 139 | - **`src/agent/`**: AI-powered analysis and decision support 140 | 141 | ### Interface Modules 142 | - **`src/ui/`**: All user interface implementations 143 | - **`core_cli.py`**: Base CLI abstraction providing shared functionality 144 | - **`simple_cli.py`**: Basic CLI interface inheriting from core 145 | - **`modern_cli.py`**: Advanced CLI interface using core utilities 146 | - **`src/utils/`**: Shared functionality across modules 147 | 148 | ### CLI Architecture (New) 149 | The CLI system now uses an abstract base class to eliminate code duplication: 150 | 151 | ``` 152 | core_cli.py (BaseCLI class) 153 | ├── Shared command implementations (account info, config validation, etc.) 154 | ├── Common error handling and display utilities 155 | ├── Base table creation and formatting functions 156 | └── Abstract methods for custom command setup 157 | 158 | simple_cli.py (SimpleCLI class inheriting from BaseCLI) 159 | ├── Basic command registration 160 | ├── Simple momentum strategy runner 161 | └── Fallback mode for systems without rich/typer 162 | 163 | modern_cli.py (Uses BaseCLI utilities) 164 | ├── Advanced async operations 165 | ├── Sub-command organization 166 | ├── Progress bars and live displays 167 | └── Comprehensive trading features 168 | ``` 169 | 170 | ### Entry Point Selection (New) 171 | The `main.py` entry point now supports CLI interface selection: 172 | 173 | ```bash 174 | # Use modern CLI (default) 175 | python main.py [commands] 176 | 177 | # Explicitly choose CLI interface 178 | python main.py --cli modern [commands] # Rich CLI with sub-commands 179 | python main.py --cli simple [commands] # Simple flat command structure 180 | 181 | # Examples 182 | python main.py account info # Modern: sub-command syntax 183 | python main.py --cli simple account-info # Simple: flat command syntax 184 | ``` 185 | 186 | **Benefits:** 187 | - **Modern CLI**: Rich interface, sub-commands, async operations, advanced features 188 | - **Simple CLI**: Lightweight, flat commands, basic functionality, fallback compatibility 189 | - **Unified Entry**: Single entry point with automatic CLI selection and argument forwarding 190 | 191 | ## Import Guidelines 192 | 193 | ### Correct Import Patterns 194 | ```python 195 | # Trading functionality 196 | from src.trade import Backtester, Portfolio 197 | from src.strategy import MomentumStrategy 198 | 199 | # Data providers 200 | from src.data import AlpacaDataProvider 201 | 202 | # Utilities 203 | from src.utils import timezone_utils, dashboard_utils 204 | ``` 205 | 206 | ### Updated Import Patterns 207 | ```python 208 | # Performance analysis now in analysis module 209 | from src.analysis import PerformanceAnalyzer 210 | 211 | # Backtesting functionality in trade module 212 | from src.trade import Backtester, Portfolio 213 | ``` 214 | 215 | ## File Organization Rules 216 | 217 | 1. **Entry Points**: Root level (`main.py`) 218 | 2. **Examples**: `examples/` directory with README 219 | 3. **Source Code**: `src/` with logical module separation 220 | 4. **Configuration**: `config/` directory 221 | 5. **Tests**: `tests/` directory 222 | 6. **Results**: `results/` for generated outputs 223 | 7. **Documentation**: `docs/` for project documentation 224 | 225 | ## Benefits of This Structure 226 | 227 | 1. **Maintainability**: Clear module boundaries make changes easier 228 | 2. **Testability**: Isolated components are easier to test 229 | 3. **Scalability**: New features fit into existing structure 230 | 4. **Code Reuse**: Shared utilities eliminate duplication 231 | 5. **Team Collaboration**: Clear ownership of different modules 232 | 233 | This structure follows modern Python project conventions and supports the project's growth while maintaining code quality and organization. -------------------------------------------------------------------------------- /src/account/account_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Account Configuration Manager 3 | 4 | Handles Alpaca account configurations and settings 5 | Reference: https://docs.alpaca.markets/reference/patchaccountconfig-1 6 | """ 7 | 8 | import requests 9 | import logging 10 | from typing import Dict, List, Any, Optional 11 | from datetime import datetime 12 | 13 | class AccountConfigurator: 14 | """Manages Alpaca account configurations""" 15 | 16 | def __init__(self, account_manager): 17 | self.account_manager = account_manager 18 | self.logger = logging.getLogger(__name__) 19 | 20 | def get_account_configurations(self) -> Dict[str, Any]: 21 | """Get current account configurations""" 22 | try: 23 | response = requests.get( 24 | f"{self.account_manager.base_url}/v2/account/configurations", 25 | headers=self.account_manager.headers, 26 | timeout=10 27 | ) 28 | response.raise_for_status() 29 | 30 | configs = response.json() 31 | self.logger.info("Account configurations retrieved successfully") 32 | 33 | return configs 34 | 35 | except Exception as e: 36 | self.logger.error(f"Error retrieving account configurations: {e}") 37 | return {"error": str(e)} 38 | 39 | def update_account_configurations(self, configurations: Dict[str, Any]) -> Dict[str, Any]: 40 | """Update account configurations 41 | 42 | Available configurations: 43 | - day_trade_margin_call: EQUITY or CASH 44 | - trade_confirm_email: EMAIL or ALL or NONE 45 | - suspend_trade: true or false 46 | - no_shorting: true or false 47 | - fractional_trading: true or false 48 | - max_margin_multiplier: float (1.0 to 4.0) 49 | - pdt_check: ENTRY or EXIT or BOTH or NONE 50 | - trading_hours: STANDARD or EXTENDED 51 | """ 52 | 53 | # Validate configuration values 54 | valid_configs = self._validate_configurations(configurations) 55 | if 'error' in valid_configs: 56 | return valid_configs 57 | 58 | try: 59 | response = requests.patch( 60 | f"{self.account_manager.base_url}/v2/account/configurations", 61 | headers=self.account_manager.headers, 62 | json=configurations, 63 | timeout=10 64 | ) 65 | response.raise_for_status() 66 | 67 | updated_configs = response.json() 68 | self.logger.info("Account configurations updated successfully") 69 | 70 | return updated_configs 71 | 72 | except Exception as e: 73 | self.logger.error(f"Error updating account configurations: {e}") 74 | return {"error": str(e)} 75 | 76 | def _validate_configurations(self, configurations: Dict[str, Any]) -> Dict[str, Any]: 77 | """Validate configuration parameters""" 78 | valid_options = { 79 | 'day_trade_margin_call': ['EQUITY', 'CASH'], 80 | 'trade_confirm_email': ['EMAIL', 'ALL', 'NONE'], 81 | 'suspend_trade': [True, False], 82 | 'no_shorting': [True, False], 83 | 'fractional_trading': [True, False], 84 | 'pdt_check': ['ENTRY', 'EXIT', 'BOTH', 'NONE'], 85 | 'trading_hours': ['STANDARD', 'EXTENDED'] 86 | } 87 | 88 | errors = [] 89 | 90 | for key, value in configurations.items(): 91 | if key in valid_options: 92 | if key == 'max_margin_multiplier': 93 | # Special validation for margin multiplier 94 | try: 95 | float_val = float(value) 96 | if not (1.0 <= float_val <= 4.0): 97 | errors.append(f"max_margin_multiplier must be between 1.0 and 4.0") 98 | except (ValueError, TypeError): 99 | errors.append(f"max_margin_multiplier must be a number") 100 | elif value not in valid_options[key]: 101 | errors.append(f"Invalid value for {key}: {value}. Valid options: {valid_options[key]}") 102 | elif key != 'max_margin_multiplier': 103 | errors.append(f"Unknown configuration parameter: {key}") 104 | 105 | if errors: 106 | return {"error": f"Validation errors: {'; '.join(errors)}"} 107 | 108 | return {"valid": True} 109 | 110 | def enable_extended_hours_trading(self) -> Dict[str, Any]: 111 | """Enable extended hours trading""" 112 | return self.update_account_configurations({ 113 | 'trading_hours': 'EXTENDED' 114 | }) 115 | 116 | def disable_extended_hours_trading(self) -> Dict[str, Any]: 117 | """Disable extended hours trading (standard hours only)""" 118 | return self.update_account_configurations({ 119 | 'trading_hours': 'STANDARD' 120 | }) 121 | 122 | def enable_fractional_trading(self) -> Dict[str, Any]: 123 | """Enable fractional share trading""" 124 | return self.update_account_configurations({ 125 | 'fractional_trading': True 126 | }) 127 | 128 | def disable_fractional_trading(self) -> Dict[str, Any]: 129 | """Disable fractional share trading""" 130 | return self.update_account_configurations({ 131 | 'fractional_trading': False 132 | }) 133 | 134 | def set_pdt_check(self, check_type: str = 'BOTH') -> Dict[str, Any]: 135 | """Set Pattern Day Trader check 136 | 137 | Options: 138 | - ENTRY: Check before order entry 139 | - EXIT: Check before order exit 140 | - BOTH: Check on both entry and exit 141 | - NONE: No PDT checking 142 | """ 143 | return self.update_account_configurations({ 144 | 'pdt_check': check_type.upper() 145 | }) 146 | 147 | def set_margin_multiplier(self, multiplier: float) -> Dict[str, Any]: 148 | """Set maximum margin multiplier (1.0 to 4.0)""" 149 | return self.update_account_configurations({ 150 | 'max_margin_multiplier': multiplier 151 | }) 152 | 153 | def enable_shorting(self) -> Dict[str, Any]: 154 | """Enable short selling""" 155 | return self.update_account_configurations({ 156 | 'no_shorting': False 157 | }) 158 | 159 | def disable_shorting(self) -> Dict[str, Any]: 160 | """Disable short selling""" 161 | return self.update_account_configurations({ 162 | 'no_shorting': True 163 | }) 164 | 165 | def set_trade_confirmation_email(self, email_type: str = 'ALL') -> Dict[str, Any]: 166 | """Set trade confirmation email preferences 167 | 168 | Options: 169 | - EMAIL: Send to email address 170 | - ALL: Send all confirmations 171 | - NONE: No email confirmations 172 | """ 173 | return self.update_account_configurations({ 174 | 'trade_confirm_email': email_type.upper() 175 | }) 176 | 177 | def suspend_trading(self) -> Dict[str, Any]: 178 | """Suspend trading on the account""" 179 | return self.update_account_configurations({ 180 | 'suspend_trade': True 181 | }) 182 | 183 | def resume_trading(self) -> Dict[str, Any]: 184 | """Resume trading on the account""" 185 | return self.update_account_configurations({ 186 | 'suspend_trade': False 187 | }) 188 | 189 | def get_configuration_summary(self) -> str: 190 | """Get formatted configuration summary""" 191 | configs = self.get_account_configurations() 192 | 193 | if 'error' in configs: 194 | return f"Error retrieving configurations: {configs['error']}" 195 | 196 | summary = f""" 197 | 🌍 GAUSS WORLD TRADER - ACCOUNT CONFIGURATIONS 198 | ============================================= 199 | Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 200 | 201 | TRADING SETTINGS: 202 | ---------------- 203 | • Trading Hours: {configs.get('trading_hours', 'N/A')} 204 | • Extended Hours: {'Enabled' if configs.get('trading_hours') == 'EXTENDED' else 'Disabled'} 205 | • Fractional Trading: {'Enabled' if configs.get('fractional_trading', False) else 'Disabled'} 206 | • Short Selling: {'Disabled' if configs.get('no_shorting', False) else 'Enabled'} 207 | • Trading Suspended: {'Yes' if configs.get('suspend_trade', False) else 'No'} 208 | 209 | MARGIN SETTINGS: 210 | --------------- 211 | • Day Trade Margin Call: {configs.get('day_trade_margin_call', 'N/A')} 212 | • Max Margin Multiplier: {configs.get('max_margin_multiplier', 'N/A')} 213 | • PDT Check: {configs.get('pdt_check', 'N/A')} 214 | 215 | NOTIFICATIONS: 216 | ------------- 217 | • Trade Confirmation Email: {configs.get('trade_confirm_email', 'N/A')} 218 | 219 | Using: {"Paper Trading" if "paper" in self.account_manager.base_url else "Live Trading"} 220 | """ 221 | 222 | return summary 223 | 224 | def apply_conservative_settings(self) -> Dict[str, Any]: 225 | """Apply conservative trading settings""" 226 | conservative_config = { 227 | 'trading_hours': 'STANDARD', 228 | 'fractional_trading': False, 229 | 'no_shorting': True, 230 | 'pdt_check': 'BOTH', 231 | 'max_margin_multiplier': 1.0, 232 | 'trade_confirm_email': 'ALL' 233 | } 234 | 235 | result = self.update_account_configurations(conservative_config) 236 | 237 | if 'error' not in result: 238 | self.logger.info("Conservative trading settings applied") 239 | 240 | return result 241 | 242 | def apply_aggressive_settings(self) -> Dict[str, Any]: 243 | """Apply aggressive trading settings""" 244 | aggressive_config = { 245 | 'trading_hours': 'EXTENDED', 246 | 'fractional_trading': True, 247 | 'no_shorting': False, 248 | 'pdt_check': 'NONE', 249 | 'max_margin_multiplier': 4.0, 250 | 'trade_confirm_email': 'NONE' 251 | } 252 | 253 | result = self.update_account_configurations(aggressive_config) 254 | 255 | if 'error' not in result: 256 | self.logger.info("Aggressive trading settings applied") 257 | 258 | return result -------------------------------------------------------------------------------- /src/utils/error_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Modern error handling using Python 3.12 features 3 | Includes exception groups, improved error messages, and structured error handling 4 | """ 5 | from __future__ import annotations 6 | 7 | import traceback 8 | from datetime import datetime, timedelta 9 | from typing import Any, TYPE_CHECKING 10 | from collections.abc import Sequence 11 | import logging 12 | from contextlib import contextmanager 13 | from dataclasses import dataclass 14 | from enum import Enum 15 | 16 | if TYPE_CHECKING: 17 | from types import TracebackType 18 | 19 | class ErrorSeverity(Enum): 20 | """Error severity levels for better categorization""" 21 | LOW = "low" 22 | MEDIUM = "medium" 23 | HIGH = "high" 24 | CRITICAL = "critical" 25 | 26 | @dataclass(frozen=True) 27 | class TradingError: 28 | """Structured error information for trading operations""" 29 | error_type: str 30 | message: str 31 | severity: ErrorSeverity 32 | timestamp: datetime 33 | context: dict[str, Any] 34 | traceback_info: str | None = None 35 | 36 | def to_dict(self) -> dict[str, Any]: 37 | """Convert error to dictionary for logging/storage""" 38 | return { 39 | 'error_type': self.error_type, 40 | 'message': self.message, 41 | 'severity': self.severity.value, 42 | 'timestamp': self.timestamp.isoformat(), 43 | 'context': self.context, 44 | 'traceback': self.traceback_info 45 | } 46 | 47 | class TradingSystemError(Exception): 48 | """Base exception for trading system errors""" 49 | def __init__(self, message: str, severity: ErrorSeverity = ErrorSeverity.MEDIUM, **context) -> None: 50 | super().__init__(message) 51 | self.message = message 52 | self.severity = severity 53 | self.context = context 54 | self.timestamp = datetime.now() 55 | 56 | class DataProviderError(TradingSystemError): 57 | """Errors related to data providers""" 58 | pass 59 | 60 | class TradingEngineError(TradingSystemError): 61 | """Errors related to trading engine operations""" 62 | pass 63 | 64 | class StrategyError(TradingSystemError): 65 | """Errors related to strategy execution""" 66 | pass 67 | 68 | class RiskManagementError(TradingSystemError): 69 | """Errors related to risk management""" 70 | pass 71 | 72 | class ErrorHandler: 73 | """ 74 | Centralized error handler using Python 3.12 exception groups 75 | and improved error handling patterns 76 | """ 77 | 78 | def __init__(self) -> None: 79 | self.logger = logging.getLogger(__name__) 80 | self.error_history: list[TradingError] = [] 81 | 82 | def handle_exception_group(self, exc_group: BaseExceptionGroup) -> None: 83 | """ 84 | Handle exception groups (Python 3.11+ feature, optimized for 3.12) 85 | """ 86 | errors = [] 87 | 88 | for exc in exc_group.exceptions: 89 | if isinstance(exc, TradingSystemError): 90 | trading_error = TradingError( 91 | error_type=type(exc).__name__, 92 | message=exc.message, 93 | severity=exc.severity, 94 | timestamp=exc.timestamp, 95 | context=exc.context, 96 | traceback_info=traceback.format_exc() 97 | ) 98 | errors.append(trading_error) 99 | self._log_error(trading_error) 100 | else: 101 | # Handle non-trading system errors 102 | generic_error = TradingError( 103 | error_type=type(exc).__name__, 104 | message=str(exc), 105 | severity=ErrorSeverity.HIGH, 106 | timestamp=datetime.now(), 107 | context={}, 108 | traceback_info=traceback.format_exc() 109 | ) 110 | errors.append(generic_error) 111 | self._log_error(generic_error) 112 | 113 | self.error_history.extend(errors) 114 | 115 | def handle_single_exception(self, exc: Exception, context: dict[str, Any] | None = None) -> TradingError: 116 | """Handle a single exception with context""" 117 | context = context or {} 118 | 119 | if isinstance(exc, TradingSystemError): 120 | trading_error = TradingError( 121 | error_type=type(exc).__name__, 122 | message=exc.message, 123 | severity=exc.severity, 124 | timestamp=exc.timestamp, 125 | context={**exc.context, **context}, 126 | traceback_info=traceback.format_exc() 127 | ) 128 | else: 129 | trading_error = TradingError( 130 | error_type=type(exc).__name__, 131 | message=str(exc), 132 | severity=ErrorSeverity.MEDIUM, 133 | timestamp=datetime.now(), 134 | context=context, 135 | traceback_info=traceback.format_exc() 136 | ) 137 | 138 | self._log_error(trading_error) 139 | self.error_history.append(trading_error) 140 | return trading_error 141 | 142 | def _log_error(self, error: TradingError) -> None: 143 | """Log error with appropriate level based on severity""" 144 | log_message = f"{error.error_type}: {error.message}" 145 | if error.context: 146 | log_message += f" | Context: {error.context}" 147 | 148 | match error.severity: 149 | case ErrorSeverity.LOW: 150 | self.logger.info(log_message) 151 | case ErrorSeverity.MEDIUM: 152 | self.logger.warning(log_message) 153 | case ErrorSeverity.HIGH: 154 | self.logger.error(log_message) 155 | case ErrorSeverity.CRITICAL: 156 | self.logger.critical(log_message) 157 | 158 | def get_error_summary(self, hours_back: int = 24) -> dict[str, Any]: 159 | """Get error summary for the last N hours""" 160 | cutoff_time = datetime.now() - timedelta(hours=hours_back) 161 | recent_errors = [ 162 | err for err in self.error_history 163 | if err.timestamp >= cutoff_time 164 | ] 165 | 166 | # Count by severity and type 167 | severity_counts = {} 168 | type_counts = {} 169 | 170 | for error in recent_errors: 171 | severity_counts[error.severity.value] = severity_counts.get(error.severity.value, 0) + 1 172 | type_counts[error.error_type] = type_counts.get(error.error_type, 0) + 1 173 | 174 | return { 175 | 'total_errors': len(recent_errors), 176 | 'severity_breakdown': severity_counts, 177 | 'error_type_breakdown': type_counts, 178 | 'time_period_hours': hours_back 179 | } 180 | 181 | @contextmanager 182 | def error_context(self, operation: str, **context): 183 | """ 184 | Context manager for operations that may raise exceptions 185 | Uses Python 3.12's improved exception handling 186 | """ 187 | try: 188 | yield 189 | except Exception as e: 190 | # Handle all exceptions (including groups) 191 | enhanced_context = { 192 | 'operation': operation, 193 | **context 194 | } 195 | 196 | # Check if it's an exception group 197 | if isinstance(e, BaseExceptionGroup): 198 | self.logger.error(f"Trading system errors in operation '{operation}'") 199 | self.handle_exception_group(e) 200 | else: 201 | # Handle single exceptions 202 | self.handle_single_exception(e, enhanced_context) 203 | raise 204 | 205 | # Global error handler instance 206 | error_handler = ErrorHandler() 207 | 208 | def safe_execute(func: callable, *args, **kwargs) -> tuple[Any, TradingError | None]: 209 | """ 210 | Safely execute a function and return result with any error 211 | Uses Python 3.12's improved exception handling 212 | """ 213 | try: 214 | result = func(*args, **kwargs) 215 | return result, None 216 | except Exception as e: 217 | error = error_handler.handle_single_exception(e, {'function': func.__name__}) 218 | return None, error 219 | 220 | async def safe_execute_async(func: callable, *args, **kwargs) -> tuple[Any, TradingError | None]: 221 | """Async version of safe_execute""" 222 | try: 223 | result = await func(*args, **kwargs) 224 | return result, None 225 | except Exception as e: 226 | error = error_handler.handle_single_exception(e, {'async_function': func.__name__}) 227 | return None, error 228 | 229 | # Example usage with Python 3.12 pattern matching 230 | def handle_trading_operation_result(result: Any, error: TradingError | None) -> bool: 231 | """Handle the result of a trading operation using pattern matching""" 232 | match (result, error): 233 | case (None, TradingError(severity=ErrorSeverity.CRITICAL)): 234 | # Critical error - stop all operations 235 | logging.critical("Critical error occurred - halting operations") 236 | return False 237 | case (None, TradingError(severity=ErrorSeverity.HIGH)): 238 | # High severity - retry with caution 239 | logging.error("High severity error - implementing fallback") 240 | return False 241 | case (None, TradingError()): 242 | # Other errors - log and continue 243 | logging.warning("Operation failed but continuing") 244 | return True 245 | case (result, None): 246 | # Success 247 | logging.info("Operation completed successfully") 248 | return True 249 | case _: 250 | # Unexpected case 251 | logging.warning("Unexpected result/error combination") 252 | return True 253 | 254 | # Decorator for automatic error handling 255 | def with_error_handling(operation_name: str | None = None): 256 | """Decorator to add automatic error handling to functions""" 257 | def decorator(func: callable): 258 | def wrapper(*args, **kwargs): 259 | op_name = operation_name or func.__name__ 260 | with error_handler.error_context(op_name, function=func.__name__): 261 | return func(*args, **kwargs) 262 | return wrapper 263 | return decorator -------------------------------------------------------------------------------- /src/utils/watchlist_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Watchlist Manager 4 | Handles watchlist operations including reading, writing, adding, and removing symbols 5 | """ 6 | 7 | import json 8 | import os 9 | from datetime import datetime 10 | from pathlib import Path 11 | from typing import List, Dict, Optional 12 | import logging 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | class WatchlistManager: 17 | """Manages watchlist operations with JSON persistence""" 18 | 19 | def __init__(self, watchlist_file: Optional[str] = None): 20 | """Initialize watchlist manager 21 | 22 | Args: 23 | watchlist_file: Path to watchlist JSON file. If None, uses default location. 24 | """ 25 | if watchlist_file is None: 26 | # Default to project root 27 | project_root = Path(__file__).parent.parent.parent 28 | self.watchlist_file = project_root / "watchlist.json" 29 | else: 30 | self.watchlist_file = Path(watchlist_file) 31 | 32 | # Ensure the file exists 33 | self._ensure_watchlist_exists() 34 | 35 | def _ensure_watchlist_exists(self): 36 | """Ensure watchlist file exists with default content""" 37 | if not self.watchlist_file.exists(): 38 | default_watchlist = { 39 | "watchlist": [ 40 | "AAPL", "GOOGL", "MSFT", "TSLA", "NVDA", 41 | "AMZN", "META", "SPY", "QQQ", "VOO" 42 | ], 43 | "metadata": { 44 | "created": datetime.now().strftime("%Y-%m-%d"), 45 | "last_updated": datetime.now().strftime("%Y-%m-%d"), 46 | "description": "Gauss World Trader Default Watchlist", 47 | "version": "1.0" 48 | } 49 | } 50 | 51 | try: 52 | with open(self.watchlist_file, 'w') as f: 53 | json.dump(default_watchlist, f, indent=2) 54 | logger.info(f"Created default watchlist at {self.watchlist_file}") 55 | except Exception as e: 56 | logger.error(f"Error creating default watchlist: {e}") 57 | raise 58 | 59 | def _load_watchlist(self) -> Dict: 60 | """Load watchlist from JSON file""" 61 | try: 62 | with open(self.watchlist_file, 'r') as f: 63 | return json.load(f) 64 | except FileNotFoundError: 65 | logger.warning(f"Watchlist file not found: {self.watchlist_file}") 66 | self._ensure_watchlist_exists() 67 | return self._load_watchlist() 68 | except json.JSONDecodeError as e: 69 | logger.error(f"Error parsing watchlist JSON: {e}") 70 | raise 71 | except Exception as e: 72 | logger.error(f"Error loading watchlist: {e}") 73 | raise 74 | 75 | def _save_watchlist(self, data: Dict): 76 | """Save watchlist to JSON file""" 77 | try: 78 | # Update metadata 79 | data["metadata"]["last_updated"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 80 | 81 | with open(self.watchlist_file, 'w') as f: 82 | json.dump(data, f, indent=2) 83 | logger.info(f"Watchlist saved to {self.watchlist_file}") 84 | except Exception as e: 85 | logger.error(f"Error saving watchlist: {e}") 86 | raise 87 | 88 | def get_watchlist(self) -> List[str]: 89 | """Get current watchlist symbols 90 | 91 | Returns: 92 | List of watchlist symbols 93 | """ 94 | data = self._load_watchlist() 95 | return data.get("watchlist", []) 96 | 97 | def add_symbol(self, symbol: str) -> bool: 98 | """Add symbol to watchlist 99 | 100 | Args: 101 | symbol: Stock symbol to add 102 | 103 | Returns: 104 | True if added, False if already exists 105 | """ 106 | symbol = symbol.upper().strip() 107 | 108 | if not symbol: 109 | raise ValueError("Symbol cannot be empty") 110 | 111 | data = self._load_watchlist() 112 | watchlist = data.get("watchlist", []) 113 | 114 | if symbol in watchlist: 115 | logger.info(f"Symbol {symbol} already in watchlist") 116 | return False 117 | 118 | watchlist.append(symbol) 119 | data["watchlist"] = watchlist 120 | self._save_watchlist(data) 121 | 122 | logger.info(f"Added {symbol} to watchlist") 123 | return True 124 | 125 | def remove_symbol(self, symbol: str) -> bool: 126 | """Remove symbol from watchlist 127 | 128 | Args: 129 | symbol: Stock symbol to remove 130 | 131 | Returns: 132 | True if removed, False if not found 133 | """ 134 | symbol = symbol.upper().strip() 135 | 136 | data = self._load_watchlist() 137 | watchlist = data.get("watchlist", []) 138 | 139 | if symbol not in watchlist: 140 | logger.info(f"Symbol {symbol} not found in watchlist") 141 | return False 142 | 143 | watchlist.remove(symbol) 144 | data["watchlist"] = watchlist 145 | self._save_watchlist(data) 146 | 147 | logger.info(f"Removed {symbol} from watchlist") 148 | return True 149 | 150 | def clear_watchlist(self): 151 | """Clear all symbols from watchlist""" 152 | data = self._load_watchlist() 153 | data["watchlist"] = [] 154 | self._save_watchlist(data) 155 | logger.info("Cleared watchlist") 156 | 157 | def set_watchlist(self, symbols: List[str]): 158 | """Set entire watchlist 159 | 160 | Args: 161 | symbols: List of symbols to set as watchlist 162 | """ 163 | # Clean and validate symbols 164 | clean_symbols = [] 165 | for symbol in symbols: 166 | symbol = symbol.upper().strip() 167 | if symbol and symbol not in clean_symbols: 168 | clean_symbols.append(symbol) 169 | 170 | data = self._load_watchlist() 171 | data["watchlist"] = clean_symbols 172 | self._save_watchlist(data) 173 | 174 | logger.info(f"Set watchlist to {len(clean_symbols)} symbols") 175 | 176 | def get_watchlist_info(self) -> Dict: 177 | """Get full watchlist information including metadata 178 | 179 | Returns: 180 | Complete watchlist data including metadata 181 | """ 182 | return self._load_watchlist() 183 | 184 | def is_symbol_in_watchlist(self, symbol: str) -> bool: 185 | """Check if symbol is in watchlist 186 | 187 | Args: 188 | symbol: Stock symbol to check 189 | 190 | Returns: 191 | True if symbol is in watchlist 192 | """ 193 | symbol = symbol.upper().strip() 194 | watchlist = self.get_watchlist() 195 | return symbol in watchlist 196 | 197 | def get_watchlist_size(self) -> int: 198 | """Get number of symbols in watchlist 199 | 200 | Returns: 201 | Number of symbols in watchlist 202 | """ 203 | return len(self.get_watchlist()) 204 | 205 | def backup_watchlist(self, backup_file: Optional[str] = None) -> str: 206 | """Create backup of current watchlist 207 | 208 | Args: 209 | backup_file: Path for backup file. If None, creates timestamped backup. 210 | 211 | Returns: 212 | Path to backup file 213 | """ 214 | if backup_file is None: 215 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 216 | backup_file = f"watchlist_backup_{timestamp}.json" 217 | 218 | backup_path = Path(backup_file) 219 | 220 | # Copy current watchlist to backup 221 | data = self._load_watchlist() 222 | with open(backup_path, 'w') as f: 223 | json.dump(data, f, indent=2) 224 | 225 | logger.info(f"Watchlist backed up to {backup_path}") 226 | return str(backup_path) 227 | 228 | def restore_from_backup(self, backup_file: str): 229 | """Restore watchlist from backup 230 | 231 | Args: 232 | backup_file: Path to backup file 233 | """ 234 | backup_path = Path(backup_file) 235 | 236 | if not backup_path.exists(): 237 | raise FileNotFoundError(f"Backup file not found: {backup_path}") 238 | 239 | try: 240 | with open(backup_path, 'r') as f: 241 | data = json.load(f) 242 | 243 | # Validate backup data 244 | if "watchlist" not in data: 245 | raise ValueError("Invalid backup file: missing watchlist") 246 | 247 | self._save_watchlist(data) 248 | logger.info(f"Watchlist restored from {backup_path}") 249 | 250 | except Exception as e: 251 | logger.error(f"Error restoring from backup: {e}") 252 | raise 253 | 254 | # Convenience functions for global usage 255 | _global_manager = None 256 | 257 | def get_watchlist_manager() -> WatchlistManager: 258 | """Get global watchlist manager instance""" 259 | global _global_manager 260 | if _global_manager is None: 261 | _global_manager = WatchlistManager() 262 | return _global_manager 263 | 264 | def get_default_watchlist() -> List[str]: 265 | """Get default watchlist symbols 266 | 267 | Returns: 268 | List of default watchlist symbols 269 | """ 270 | manager = get_watchlist_manager() 271 | return manager.get_watchlist() 272 | 273 | def add_to_watchlist(symbol: str) -> bool: 274 | """Add symbol to default watchlist 275 | 276 | Args: 277 | symbol: Stock symbol to add 278 | 279 | Returns: 280 | True if added, False if already exists 281 | """ 282 | manager = get_watchlist_manager() 283 | return manager.add_symbol(symbol) 284 | 285 | def remove_from_watchlist(symbol: str) -> bool: 286 | """Remove symbol from default watchlist 287 | 288 | Args: 289 | symbol: Stock symbol to remove 290 | 291 | Returns: 292 | True if removed, False if not found 293 | """ 294 | manager = get_watchlist_manager() 295 | return manager.remove_symbol(symbol) -------------------------------------------------------------------------------- /src/analysis/performance_analyzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Performance Analysis Module for Backtesting 3 | 4 | Enhanced performance metrics and visualization capabilities 5 | """ 6 | 7 | import pandas as pd 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | from typing import Dict, List, Any, Optional 11 | from datetime import datetime 12 | import seaborn as sns 13 | 14 | class PerformanceAnalyzer: 15 | """Advanced performance analysis for backtest results""" 16 | 17 | def __init__(self, backtest_results: Dict[str, Any]): 18 | self.results = backtest_results 19 | self.portfolio_history = backtest_results.get('portfolio_history', pd.DataFrame()) 20 | self.trades_history = backtest_results.get('trades_history', pd.DataFrame()) 21 | self.daily_returns = backtest_results.get('daily_returns', []) 22 | 23 | def calculate_advanced_metrics(self) -> Dict[str, float]: 24 | """Calculate advanced performance metrics""" 25 | if self.daily_returns: 26 | returns = np.array(self.daily_returns) 27 | 28 | # Risk metrics 29 | var_95 = np.percentile(returns, 5) # Value at Risk (95%) 30 | cvar_95 = returns[returns <= var_95].mean() # Conditional VaR 31 | 32 | # Ratios 33 | sortino_ratio = self._calculate_sortino_ratio(returns) 34 | calmar_ratio = self._calculate_calmar_ratio() 35 | 36 | # Rolling metrics 37 | rolling_sharpe = self._calculate_rolling_sharpe(returns) 38 | rolling_volatility = self._calculate_rolling_volatility(returns) 39 | 40 | return { 41 | 'value_at_risk_95': var_95, 42 | 'conditional_var_95': cvar_95, 43 | 'sortino_ratio': sortino_ratio, 44 | 'calmar_ratio': calmar_ratio, 45 | 'avg_rolling_sharpe': np.mean(rolling_sharpe) if rolling_sharpe else 0, 46 | 'avg_rolling_volatility': np.mean(rolling_volatility) if rolling_volatility else 0, 47 | 'skewness': self._calculate_skewness(returns), 48 | 'kurtosis': self._calculate_kurtosis(returns), 49 | 'tail_ratio': self._calculate_tail_ratio(returns) 50 | } 51 | 52 | return {} 53 | 54 | def _calculate_sortino_ratio(self, returns: np.ndarray, target_return: float = 0.0) -> float: 55 | """Calculate Sortino ratio (downside deviation focus)""" 56 | excess_returns = returns - target_return 57 | downside_returns = excess_returns[excess_returns < 0] 58 | 59 | if len(downside_returns) == 0: 60 | return float('inf') 61 | 62 | downside_deviation = np.sqrt(np.mean(downside_returns ** 2)) * np.sqrt(252) 63 | 64 | if downside_deviation == 0: 65 | return float('inf') 66 | 67 | return (np.mean(excess_returns) * 252) / downside_deviation 68 | 69 | def _calculate_calmar_ratio(self) -> float: 70 | """Calculate Calmar ratio (annual return / max drawdown)""" 71 | annual_return = self.results.get('annualized_return', 0) 72 | max_drawdown = self.results.get('max_drawdown', 0) 73 | 74 | if max_drawdown == 0: 75 | return float('inf') 76 | 77 | return annual_return / max_drawdown 78 | 79 | def _calculate_rolling_sharpe(self, returns: np.ndarray, window: int = 30) -> List[float]: 80 | """Calculate rolling Sharpe ratio""" 81 | if len(returns) < window: 82 | return [] 83 | 84 | rolling_sharpe = [] 85 | for i in range(window, len(returns) + 1): 86 | window_returns = returns[i-window:i] 87 | mean_return = np.mean(window_returns) 88 | std_return = np.std(window_returns) 89 | 90 | if std_return > 0: 91 | sharpe = (mean_return * np.sqrt(252)) / (std_return * np.sqrt(252)) 92 | rolling_sharpe.append(sharpe) 93 | 94 | return rolling_sharpe 95 | 96 | def _calculate_rolling_volatility(self, returns: np.ndarray, window: int = 30) -> List[float]: 97 | """Calculate rolling volatility""" 98 | if len(returns) < window: 99 | return [] 100 | 101 | rolling_vol = [] 102 | for i in range(window, len(returns) + 1): 103 | window_returns = returns[i-window:i] 104 | volatility = np.std(window_returns) * np.sqrt(252) 105 | rolling_vol.append(volatility) 106 | 107 | return rolling_vol 108 | 109 | def _calculate_skewness(self, returns: np.ndarray) -> float: 110 | """Calculate skewness of returns""" 111 | if len(returns) < 3: 112 | return 0 113 | 114 | mean_return = np.mean(returns) 115 | std_return = np.std(returns) 116 | 117 | if std_return == 0: 118 | return 0 119 | 120 | return np.mean(((returns - mean_return) / std_return) ** 3) 121 | 122 | def _calculate_kurtosis(self, returns: np.ndarray) -> float: 123 | """Calculate kurtosis of returns""" 124 | if len(returns) < 4: 125 | return 0 126 | 127 | mean_return = np.mean(returns) 128 | std_return = np.std(returns) 129 | 130 | if std_return == 0: 131 | return 0 132 | 133 | return np.mean(((returns - mean_return) / std_return) ** 4) - 3 134 | 135 | def _calculate_tail_ratio(self, returns: np.ndarray) -> float: 136 | """Calculate tail ratio (95th percentile / 5th percentile)""" 137 | if len(returns) < 20: 138 | return 0 139 | 140 | percentile_95 = np.percentile(returns, 95) 141 | percentile_5 = np.percentile(returns, 5) 142 | 143 | if percentile_5 == 0: 144 | return float('inf') 145 | 146 | return abs(percentile_95 / percentile_5) 147 | 148 | def generate_performance_report(self) -> str: 149 | """Generate comprehensive performance report""" 150 | base_metrics = self.results 151 | advanced_metrics = self.calculate_advanced_metrics() 152 | 153 | report = f""" 154 | 🌍 Gauss World Trader - Performance Analysis Report 155 | ================================================ 156 | 157 | BASIC METRICS: 158 | -------------- 159 | • Period: {base_metrics.get('start_date', 'N/A')} to {base_metrics.get('end_date', 'N/A')} 160 | • Initial Value: ${base_metrics.get('initial_value', 0):,.2f} 161 | • Final Value: ${base_metrics.get('final_value', 0):,.2f} 162 | • Total Return: {base_metrics.get('total_return_percentage', 0):.2f}% 163 | • Annualized Return: {base_metrics.get('annualized_return_percentage', 0):.2f}% 164 | • Volatility: {base_metrics.get('volatility', 0):.2f} 165 | • Sharpe Ratio: {base_metrics.get('sharpe_ratio', 0):.2f} 166 | • Max Drawdown: {base_metrics.get('max_drawdown_percentage', 0):.2f}% 167 | 168 | ADVANCED RISK METRICS: 169 | --------------------- 170 | • Value at Risk (95%): {advanced_metrics.get('value_at_risk_95', 0):.4f} 171 | • Conditional VaR (95%): {advanced_metrics.get('conditional_var_95', 0):.4f} 172 | • Sortino Ratio: {advanced_metrics.get('sortino_ratio', 0):.2f} 173 | • Calmar Ratio: {advanced_metrics.get('calmar_ratio', 0):.2f} 174 | • Skewness: {advanced_metrics.get('skewness', 0):.2f} 175 | • Kurtosis: {advanced_metrics.get('kurtosis', 0):.2f} 176 | • Tail Ratio: {advanced_metrics.get('tail_ratio', 0):.2f} 177 | 178 | TRADING STATISTICS: 179 | ------------------ 180 | • Total Trades: {base_metrics.get('total_trades', 0)} 181 | • Winning Trades: {base_metrics.get('winning_trades', 0)} 182 | • Losing Trades: {base_metrics.get('losing_trades', 0)} 183 | • Win Rate: {base_metrics.get('win_rate', 0):.2f}% 184 | • Profit Factor: {base_metrics.get('profit_factor', 0):.2f} 185 | • Total Profit: ${base_metrics.get('total_profit', 0):,.2f} 186 | • Total Loss: ${base_metrics.get('total_loss', 0):,.2f} 187 | 188 | ROLLING METRICS: 189 | --------------- 190 | • Avg Rolling Sharpe (30d): {advanced_metrics.get('avg_rolling_sharpe', 0):.2f} 191 | • Avg Rolling Volatility (30d): {advanced_metrics.get('avg_rolling_volatility', 0):.2f} 192 | """ 193 | 194 | return report 195 | 196 | def plot_performance_charts(self, save_path: Optional[str] = None) -> None: 197 | """Generate performance visualization charts""" 198 | if self.portfolio_history.empty: 199 | print("No portfolio history data available for plotting") 200 | return 201 | 202 | fig, axes = plt.subplots(2, 2, figsize=(15, 10)) 203 | fig.suptitle('Gauss World Trader - Performance Analysis', fontsize=16) 204 | 205 | # Portfolio value over time 206 | axes[0, 0].plot(self.portfolio_history['date'], self.portfolio_history['portfolio_value']) 207 | axes[0, 0].set_title('Portfolio Value Over Time') 208 | axes[0, 0].set_xlabel('Date') 209 | axes[0, 0].set_ylabel('Portfolio Value ($)') 210 | axes[0, 0].tick_params(axis='x', rotation=45) 211 | 212 | # Drawdown chart 213 | if 'drawdowns' in self.results: 214 | drawdowns = self.results['drawdowns'] 215 | axes[0, 1].fill_between(range(len(drawdowns)), drawdowns, alpha=0.7, color='red') 216 | axes[0, 1].set_title('Drawdown Over Time') 217 | axes[0, 1].set_xlabel('Trading Days') 218 | axes[0, 1].set_ylabel('Drawdown (%)') 219 | 220 | # Daily returns distribution 221 | if self.daily_returns: 222 | axes[1, 0].hist(self.daily_returns, bins=50, alpha=0.7, edgecolor='black') 223 | axes[1, 0].set_title('Daily Returns Distribution') 224 | axes[1, 0].set_xlabel('Daily Return') 225 | axes[1, 0].set_ylabel('Frequency') 226 | 227 | # Rolling Sharpe ratio 228 | if self.daily_returns: 229 | rolling_sharpe = self._calculate_rolling_sharpe(np.array(self.daily_returns)) 230 | if rolling_sharpe: 231 | axes[1, 1].plot(rolling_sharpe) 232 | axes[1, 1].set_title('Rolling Sharpe Ratio (30-day)') 233 | axes[1, 1].set_xlabel('Trading Days') 234 | axes[1, 1].set_ylabel('Sharpe Ratio') 235 | axes[1, 1].axhline(y=0, color='red', linestyle='--', alpha=0.5) 236 | 237 | plt.tight_layout() 238 | 239 | if save_path: 240 | plt.savefig(save_path, dpi=300, bbox_inches='tight') 241 | print(f"Performance charts saved to: {save_path}") 242 | else: 243 | plt.show() 244 | 245 | plt.close() -------------------------------------------------------------------------------- /src/trade/trading_engine.py: -------------------------------------------------------------------------------- 1 | from alpaca.trading.client import TradingClient 2 | from alpaca.trading.requests import ( 3 | MarketOrderRequest, LimitOrderRequest, StopOrderRequest, 4 | GetOrdersRequest, ClosePositionRequest 5 | ) 6 | from alpaca.trading.enums import OrderSide, OrderType, TimeInForce 7 | from alpaca.common.exceptions import APIError 8 | from typing import Dict, Any, Optional, List 9 | from datetime import datetime 10 | import logging 11 | from config import Config 12 | from .portfolio import Portfolio 13 | 14 | class TradingEngine: 15 | def __init__(self, paper_trading: bool = True): 16 | if not Config.validate_alpaca_config(): 17 | raise ValueError("Alpaca API credentials not configured") 18 | 19 | self.api = TradingClient( 20 | api_key=Config.ALPACA_API_KEY, 21 | secret_key=Config.ALPACA_SECRET_KEY, 22 | paper=Config.ALPACA_BASE_URL != "https://api.alpaca.markets" 23 | ) 24 | 25 | self.paper_trading = paper_trading 26 | self.portfolio = Portfolio() 27 | self.logger = logging.getLogger(__name__) 28 | 29 | if paper_trading: 30 | self.logger.info("Trading engine initialized in PAPER TRADING mode") 31 | else: 32 | self.logger.warning("Trading engine initialized in LIVE TRADING mode") 33 | 34 | def place_market_order(self, symbol: str, qty: int, side: str = 'buy', 35 | time_in_force: str = 'gtc') -> Dict[str, Any]: 36 | try: 37 | order_request = MarketOrderRequest( 38 | symbol=symbol, 39 | qty=abs(qty), 40 | side=OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL, 41 | time_in_force=TimeInForce.GTC if time_in_force == 'gtc' else TimeInForce.DAY 42 | ) 43 | order = self.api.submit_order(order_request) 44 | 45 | order_dict = { 46 | 'id': order.id, 47 | 'symbol': order.symbol, 48 | 'qty': float(order.qty), 49 | 'side': order.side, 50 | 'type': order.type, 51 | 'status': order.status, 52 | 'submitted_at': order.submitted_at, 53 | 'filled_at': order.filled_at, 54 | 'filled_qty': float(order.filled_qty) if order.filled_qty else 0, 55 | 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None 56 | } 57 | 58 | self.logger.info(f"Market order placed: {side} {qty} shares of {symbol}") 59 | return order_dict 60 | 61 | except Exception as e: 62 | self.logger.error(f"Failed to place market order: {e}") 63 | raise 64 | 65 | def place_limit_order(self, symbol: str, qty: int, limit_price: float, 66 | side: str = 'buy', time_in_force: str = 'gtc') -> Dict[str, Any]: 67 | try: 68 | order_request = LimitOrderRequest( 69 | symbol=symbol, 70 | qty=abs(qty), 71 | side=OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL, 72 | time_in_force=TimeInForce.GTC if time_in_force == 'gtc' else TimeInForce.DAY, 73 | limit_price=limit_price 74 | ) 75 | order = self.api.submit_order(order_request) 76 | 77 | order_dict = { 78 | 'id': order.id, 79 | 'symbol': order.symbol, 80 | 'qty': float(order.qty), 81 | 'side': order.side, 82 | 'type': order.type, 83 | 'limit_price': float(order.limit_price), 84 | 'status': order.status, 85 | 'submitted_at': order.submitted_at, 86 | 'filled_at': order.filled_at, 87 | 'filled_qty': float(order.filled_qty) if order.filled_qty else 0, 88 | 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None 89 | } 90 | 91 | self.logger.info(f"Limit order placed: {side} {qty} shares of {symbol} at ${limit_price}") 92 | return order_dict 93 | 94 | except Exception as e: 95 | self.logger.error(f"Failed to place limit order: {e}") 96 | raise 97 | 98 | def place_stop_loss_order(self, symbol: str, qty: int, stop_price: float, 99 | side: str = 'sell', time_in_force: str = 'gtc') -> Dict[str, Any]: 100 | try: 101 | order_request = StopOrderRequest( 102 | symbol=symbol, 103 | qty=abs(qty), 104 | side=OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL, 105 | time_in_force=TimeInForce.GTC if time_in_force == 'gtc' else TimeInForce.DAY, 106 | stop_price=stop_price 107 | ) 108 | order = self.api.submit_order(order_request) 109 | 110 | order_dict = { 111 | 'id': order.id, 112 | 'symbol': order.symbol, 113 | 'qty': float(order.qty), 114 | 'side': order.side, 115 | 'type': order.type, 116 | 'stop_price': float(order.stop_price), 117 | 'status': order.status, 118 | 'submitted_at': order.submitted_at 119 | } 120 | 121 | self.logger.info(f"Stop loss order placed: {side} {qty} shares of {symbol} at ${stop_price}") 122 | return order_dict 123 | 124 | except Exception as e: 125 | self.logger.error(f"Failed to place stop loss order: {e}") 126 | raise 127 | 128 | def cancel_order(self, order_id: str) -> bool: 129 | try: 130 | self.api.cancel_order(order_id) 131 | self.logger.info(f"Order {order_id} cancelled successfully") 132 | return True 133 | except Exception as e: 134 | self.logger.error(f"Failed to cancel order {order_id}: {e}") 135 | return False 136 | 137 | def get_order_status(self, order_id: str) -> Dict[str, Any]: 138 | try: 139 | order = self.api.get_order(order_id) 140 | return { 141 | 'id': order.id, 142 | 'symbol': order.symbol, 143 | 'qty': float(order.qty), 144 | 'side': order.side, 145 | 'type': order.type, 146 | 'status': order.status, 147 | 'submitted_at': order.submitted_at, 148 | 'filled_at': order.filled_at, 149 | 'filled_qty': float(order.filled_qty) if order.filled_qty else 0, 150 | 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None 151 | } 152 | except Exception as e: 153 | self.logger.error(f"Failed to get order status: {e}") 154 | return {} 155 | 156 | def get_open_orders(self) -> List[Dict[str, Any]]: 157 | try: 158 | orders = self.api.list_orders(status='open') 159 | return [{ 160 | 'id': order.id, 161 | 'symbol': order.symbol, 162 | 'qty': float(order.qty), 163 | 'side': order.side, 164 | 'type': order.type, 165 | 'status': order.status, 166 | 'submitted_at': order.submitted_at, 167 | 'limit_price': float(order.limit_price) if order.limit_price else None, 168 | 'stop_price': float(order.stop_price) if order.stop_price else None 169 | } for order in orders] 170 | except Exception as e: 171 | self.logger.error(f"Failed to get open orders: {e}") 172 | return [] 173 | 174 | def get_account_info(self) -> Dict[str, Any]: 175 | try: 176 | account = self.api.get_account() 177 | return { 178 | 'account_id': account.id, 179 | 'buying_power': float(account.buying_power), 180 | 'cash': float(account.cash), 181 | 'portfolio_value': float(account.portfolio_value), 182 | 'equity': float(account.equity), 183 | 'day_trade_count': int(getattr(account, 'day_trade_count', 0)), 184 | 'pattern_day_trader': getattr(account, 'pattern_day_trader', False), 185 | 'trading_blocked': getattr(account, 'trading_blocked', False), 186 | 'transfers_blocked': getattr(account, 'transfers_blocked', False), 187 | 'account_blocked': getattr(account, 'account_blocked', False), 188 | 'status': getattr(account, 'status', 'UNKNOWN') 189 | } 190 | except Exception as e: 191 | self.logger.error(f"Failed to get account info: {e}") 192 | return {} 193 | 194 | def get_current_positions(self) -> List[Dict[str, Any]]: 195 | try: 196 | from src.utils.validators import convert_crypto_symbol_for_display 197 | positions = self.api.get_all_positions() 198 | return [{ 199 | 'symbol': convert_crypto_symbol_for_display(pos.symbol), 200 | 'qty': float(pos.qty), 201 | 'side': pos.side.value if hasattr(pos.side, 'value') else str(pos.side), 202 | 'market_value': float(pos.market_value), 203 | 'cost_basis': float(pos.cost_basis), 204 | 'unrealized_pl': float(pos.unrealized_pl), 205 | 'unrealized_plpc': float(pos.unrealized_plpc), 206 | 'current_price': float(pos.current_price) if pos.current_price else None 207 | } for pos in positions] 208 | except Exception as e: 209 | self.logger.error(f"Failed to get positions: {e}") 210 | return [] 211 | 212 | def close_position(self, symbol: str, percentage: float = 1.0) -> Dict[str, Any]: 213 | try: 214 | positions = self.get_current_positions() 215 | position = next((p for p in positions if p['symbol'] == symbol), None) 216 | 217 | if not position: 218 | raise ValueError(f"No position found for symbol {symbol}") 219 | 220 | qty_to_close = int(abs(float(position['qty'])) * percentage) 221 | side = 'sell' if float(position['qty']) > 0 else 'buy' 222 | 223 | return self.place_market_order(symbol, qty_to_close, side) 224 | 225 | except Exception as e: 226 | self.logger.error(f"Failed to close position for {symbol}: {e}") 227 | raise 228 | 229 | def close_all_positions(self) -> List[Dict[str, Any]]: 230 | results = [] 231 | positions = self.get_current_positions() 232 | 233 | for position in positions: 234 | try: 235 | result = self.close_position(position['symbol']) 236 | results.append(result) 237 | except Exception as e: 238 | self.logger.error(f"Failed to close position for {position['symbol']}: {e}") 239 | 240 | return results -------------------------------------------------------------------------------- /src/agent/agent_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Agent Manager for Coordinating AI Analysis Tasks 3 | 4 | Manages multiple AI agents and analysis workflows 5 | """ 6 | 7 | import asyncio 8 | import logging 9 | from typing import Dict, List, Any, Optional 10 | from datetime import datetime 11 | import json 12 | 13 | from .fundamental_analyzer import FundamentalAnalyzer 14 | from .llm_providers import get_available_providers, create_provider 15 | 16 | class AgentManager: 17 | """Manages AI agents and analysis workflows""" 18 | 19 | def __init__(self, config: Dict[str, Any] = None): 20 | self.config = config or {} 21 | self.logger = logging.getLogger(__name__) 22 | self.analyzers = {} 23 | self.available_providers = get_available_providers() 24 | 25 | self.logger.info(f"Available LLM providers: {self.available_providers}") 26 | 27 | def get_analyzer(self, provider: str = 'openai') -> FundamentalAnalyzer: 28 | """Get or create fundamental analyzer for specific provider""" 29 | if provider not in self.analyzers: 30 | try: 31 | analyzer = FundamentalAnalyzer( 32 | finnhub_key=self.config.get('finnhub_api_key'), 33 | fred_key=self.config.get('fred_api_key'), 34 | llm_provider=provider 35 | ) 36 | self.analyzers[provider] = analyzer 37 | self.logger.info(f"Created analyzer with {provider} provider") 38 | except Exception as e: 39 | self.logger.error(f"Failed to create analyzer with {provider}: {e}") 40 | raise 41 | 42 | return self.analyzers[provider] 43 | 44 | def analyze_symbol(self, symbol: str, provider: str = 'openai') -> Dict[str, Any]: 45 | """Analyze a single symbol using specified provider""" 46 | try: 47 | analyzer = self.get_analyzer(provider) 48 | result = analyzer.analyze_company(symbol) 49 | 50 | self.logger.info(f"Completed analysis for {symbol} using {provider}") 51 | return result 52 | 53 | except Exception as e: 54 | self.logger.error(f"Analysis failed for {symbol} with {provider}: {e}") 55 | return { 56 | 'symbol': symbol, 57 | 'error': str(e), 58 | 'timestamp': datetime.now().isoformat(), 59 | 'provider': provider 60 | } 61 | 62 | def analyze_multiple_symbols(self, symbols: List[str], 63 | provider: str = 'openai') -> Dict[str, Dict[str, Any]]: 64 | """Analyze multiple symbols sequentially""" 65 | results = {} 66 | 67 | for symbol in symbols: 68 | self.logger.info(f"Analyzing {symbol}...") 69 | results[symbol] = self.analyze_symbol(symbol, provider) 70 | 71 | return results 72 | 73 | async def analyze_symbols_async(self, symbols: List[str], 74 | provider: str = 'openai') -> Dict[str, Dict[str, Any]]: 75 | """Analyze multiple symbols asynchronously""" 76 | async def analyze_single(symbol: str) -> tuple: 77 | """Async wrapper for single symbol analysis""" 78 | loop = asyncio.get_event_loop() 79 | result = await loop.run_in_executor( 80 | None, self.analyze_symbol, symbol, provider 81 | ) 82 | return symbol, result 83 | 84 | # Create tasks for all symbols 85 | tasks = [analyze_single(symbol) for symbol in symbols] 86 | 87 | # Execute all tasks concurrently 88 | results = await asyncio.gather(*tasks) 89 | 90 | # Convert to dictionary 91 | return {symbol: result for symbol, result in results} 92 | 93 | def compare_providers(self, symbol: str, 94 | providers: List[str] = None) -> Dict[str, Dict[str, Any]]: 95 | """Compare analysis from multiple LLM providers""" 96 | if providers is None: 97 | providers = self.available_providers 98 | 99 | if not providers: 100 | self.logger.warning("No LLM providers available") 101 | return {} 102 | 103 | results = {} 104 | 105 | for provider in providers: 106 | try: 107 | self.logger.info(f"Analyzing {symbol} with {provider}") 108 | results[provider] = self.analyze_symbol(symbol, provider) 109 | except Exception as e: 110 | self.logger.error(f"Failed to analyze {symbol} with {provider}: {e}") 111 | results[provider] = { 112 | 'error': str(e), 113 | 'symbol': symbol, 114 | 'provider': provider, 115 | 'timestamp': datetime.now().isoformat() 116 | } 117 | 118 | return results 119 | 120 | def generate_comparative_report(self, symbol: str, 121 | provider_results: Dict[str, Dict[str, Any]]) -> str: 122 | """Generate comparative analysis report from multiple providers""" 123 | 124 | report = f""" 125 | 🌍 GAUSS WORLD TRADER - COMPARATIVE ANALYSIS REPORT 126 | ================================================= 127 | Symbol: {symbol} 128 | Generated: {datetime.now().isoformat()} 129 | Providers Compared: {', '.join(provider_results.keys())} 130 | 131 | """ 132 | 133 | for provider, result in provider_results.items(): 134 | report += f""" 135 | {provider.upper()} ANALYSIS: 136 | {'-' * (len(provider) + 10)} 137 | """ 138 | 139 | if 'error' in result: 140 | report += f"❌ Error: {result['error']}\n\n" 141 | continue 142 | 143 | # Financial grades 144 | financial = result.get('financial_analysis', {}) 145 | grades = financial.get('ratio_grades', {}) 146 | if grades: 147 | report += f""" 148 | Financial Grades: 149 | • Valuation: {grades.get('valuation', 'N/A')} 150 | • Profitability: {grades.get('profitability', 'N/A')} 151 | • Liquidity: {grades.get('liquidity', 'N/A')} 152 | """ 153 | 154 | # News sentiment 155 | news = result.get('news_analysis', {}) 156 | if news and 'error' not in news: 157 | report += f"• News Sentiment: {news.get('sentiment_label', 'N/A')}\n" 158 | 159 | # Analyst consensus 160 | analyst = result.get('analyst_analysis', {}) 161 | if analyst: 162 | report += f"• Analyst Consensus: {analyst.get('consensus', 'N/A')}\n" 163 | 164 | # AI insights summary 165 | ai_insights = result.get('ai_insights') 166 | if ai_insights and isinstance(ai_insights, str) and 'error' not in ai_insights: 167 | # Extract first few sentences for summary 168 | sentences = ai_insights.split('.')[:3] 169 | summary = '. '.join(sentences) + '.' if sentences else ai_insights[:200] + '...' 170 | report += f""" 171 | AI Insights Summary: 172 | {summary} 173 | """ 174 | 175 | report += "\n" 176 | 177 | # Consensus summary 178 | report += """ 179 | CONSENSUS SUMMARY: 180 | ----------------- 181 | """ 182 | 183 | # Aggregate sentiment 184 | sentiments = [] 185 | for result in provider_results.values(): 186 | if 'error' not in result: 187 | news = result.get('news_analysis', {}) 188 | if news and 'sentiment_label' in news: 189 | sentiments.append(news['sentiment_label']) 190 | 191 | if sentiments: 192 | from collections import Counter 193 | sentiment_counts = Counter(sentiments) 194 | dominant_sentiment = sentiment_counts.most_common(1)[0][0] 195 | report += f"• Dominant News Sentiment: {dominant_sentiment}\n" 196 | 197 | # Aggregate grades 198 | all_grades = {} 199 | for result in provider_results.values(): 200 | if 'error' not in result: 201 | grades = result.get('financial_analysis', {}).get('ratio_grades', {}) 202 | for grade_type, grade in grades.items(): 203 | if grade_type not in all_grades: 204 | all_grades[grade_type] = [] 205 | all_grades[grade_type].append(grade) 206 | 207 | for grade_type, grades in all_grades.items(): 208 | if grades: 209 | from collections import Counter 210 | grade_counts = Counter(grades) 211 | dominant_grade = grade_counts.most_common(1)[0][0] 212 | report += f"• Consensus {grade_type.title()} Grade: {dominant_grade}\n" 213 | 214 | report += f""" 215 | 216 | DISCLAIMER: 217 | ---------- 218 | This comparative analysis is for informational purposes only. Results may vary 219 | between different AI providers due to their unique algorithms and training data. 220 | Please conduct your own research and consult with a qualified financial advisor. 221 | 222 | Generated by Gauss World Trader - Named after Carl Friedrich Gauss 223 | Report Timestamp: {datetime.now().isoformat()} 224 | """ 225 | 226 | return report 227 | 228 | def save_analysis(self, symbol: str, analysis_data: Dict[str, Any], 229 | filename: str = None) -> str: 230 | """Save analysis data to file""" 231 | if filename is None: 232 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 233 | filename = f"fundamental_analysis_{symbol}_{timestamp}.json" 234 | 235 | try: 236 | with open(filename, 'w', encoding='utf-8') as f: 237 | json.dump(analysis_data, f, indent=2, default=str) 238 | 239 | self.logger.info(f"Analysis saved to {filename}") 240 | return filename 241 | 242 | except Exception as e: 243 | self.logger.error(f"Failed to save analysis: {e}") 244 | raise 245 | 246 | def load_analysis(self, filename: str) -> Dict[str, Any]: 247 | """Load analysis data from file""" 248 | try: 249 | with open(filename, 'r', encoding='utf-8') as f: 250 | data = json.load(f) 251 | 252 | self.logger.info(f"Analysis loaded from {filename}") 253 | return data 254 | 255 | except Exception as e: 256 | self.logger.error(f"Failed to load analysis: {e}") 257 | raise 258 | 259 | def get_system_status(self) -> Dict[str, Any]: 260 | """Get system status and available resources""" 261 | status = { 262 | 'available_llm_providers': self.available_providers, 263 | 'active_analyzers': list(self.analyzers.keys()), 264 | 'finnhub_configured': bool(self.config.get('finnhub_api_key')), 265 | 'fred_configured': bool(self.config.get('fred_api_key')), 266 | 'timestamp': datetime.now().isoformat() 267 | } 268 | 269 | return status -------------------------------------------------------------------------------- /src/analysis/technical_analysis.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from typing import Dict, Tuple, List, Optional 4 | import logging 5 | 6 | class TechnicalAnalysis: 7 | def __init__(self): 8 | self.logger = logging.getLogger(__name__) 9 | 10 | @staticmethod 11 | def sma(data: pd.Series, period: int) -> pd.Series: 12 | return data.rolling(window=period).mean() 13 | 14 | @staticmethod 15 | def ema(data: pd.Series, period: int) -> pd.Series: 16 | return data.ewm(span=period).mean() 17 | 18 | @staticmethod 19 | def rsi(data: pd.Series, period: int = 14) -> pd.Series: 20 | delta = data.diff() 21 | gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() 22 | loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() 23 | rs = gain / loss 24 | return 100 - (100 / (1 + rs)) 25 | 26 | @staticmethod 27 | def macd(data: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9) -> Tuple[pd.Series, pd.Series, pd.Series]: 28 | ema_fast = data.ewm(span=fast).mean() 29 | ema_slow = data.ewm(span=slow).mean() 30 | macd_line = ema_fast - ema_slow 31 | signal_line = macd_line.ewm(span=signal).mean() 32 | histogram = macd_line - signal_line 33 | return macd_line, signal_line, histogram 34 | 35 | @staticmethod 36 | def bollinger_bands(data: pd.Series, period: int = 20, std_dev: float = 2) -> Tuple[pd.Series, pd.Series, pd.Series]: 37 | sma = data.rolling(window=period).mean() 38 | std = data.rolling(window=period).std() 39 | upper = sma + (std * std_dev) 40 | lower = sma - (std * std_dev) 41 | return upper, sma, lower 42 | 43 | @staticmethod 44 | def stochastic_oscillator(high: pd.Series, low: pd.Series, close: pd.Series, 45 | k_period: int = 14, d_period: int = 3) -> Tuple[pd.Series, pd.Series]: 46 | lowest_low = low.rolling(window=k_period).min() 47 | highest_high = high.rolling(window=k_period).max() 48 | k_percent = 100 * ((close - lowest_low) / (highest_high - lowest_low)) 49 | d_percent = k_percent.rolling(window=d_period).mean() 50 | return k_percent, d_percent 51 | 52 | @staticmethod 53 | def williams_r(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> pd.Series: 54 | highest_high = high.rolling(window=period).max() 55 | lowest_low = low.rolling(window=period).min() 56 | return -100 * ((highest_high - close) / (highest_high - lowest_low)) 57 | 58 | @staticmethod 59 | def atr(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> pd.Series: 60 | prev_close = close.shift(1) 61 | tr1 = high - low 62 | tr2 = (high - prev_close).abs() 63 | tr3 = (low - prev_close).abs() 64 | true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) 65 | return true_range.rolling(window=period).mean() 66 | 67 | @staticmethod 68 | def obv(close: pd.Series, volume: pd.Series) -> pd.Series: 69 | obv = pd.Series(index=close.index, dtype=float) 70 | obv.iloc[0] = volume.iloc[0] 71 | 72 | for i in range(1, len(close)): 73 | if close.iloc[i] > close.iloc[i-1]: 74 | obv.iloc[i] = obv.iloc[i-1] + volume.iloc[i] 75 | elif close.iloc[i] < close.iloc[i-1]: 76 | obv.iloc[i] = obv.iloc[i-1] - volume.iloc[i] 77 | else: 78 | obv.iloc[i] = obv.iloc[i-1] 79 | 80 | return obv 81 | 82 | @staticmethod 83 | def vwap(high: pd.Series, low: pd.Series, close: pd.Series, volume: pd.Series) -> pd.Series: 84 | typical_price = (high + low + close) / 3 85 | return (typical_price * volume).cumsum() / volume.cumsum() 86 | 87 | @staticmethod 88 | def adx(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> Tuple[pd.Series, pd.Series, pd.Series]: 89 | plus_dm = high.diff() 90 | minus_dm = low.diff() 91 | plus_dm[plus_dm < 0] = 0 92 | minus_dm[minus_dm > 0] = 0 93 | 94 | tr = TechnicalAnalysis.atr(high, low, close, 1) 95 | 96 | plus_di = 100 * (plus_dm.rolling(window=period).mean() / tr.rolling(window=period).mean()) 97 | minus_di = 100 * (minus_dm.abs().rolling(window=period).mean() / tr.rolling(window=period).mean()) 98 | 99 | dx = 100 * ((plus_di - minus_di).abs() / (plus_di + minus_di)) 100 | adx = dx.rolling(window=period).mean() 101 | 102 | return adx, plus_di, minus_di 103 | 104 | def identify_patterns(self, data: pd.DataFrame) -> Dict[str, List[int]]: 105 | patterns = { 106 | 'hammer': [], 107 | 'doji': [], 108 | 'engulfing_bullish': [], 109 | 'engulfing_bearish': [], 110 | 'morning_star': [], 111 | 'evening_star': [] 112 | } 113 | 114 | for i in range(len(data)): 115 | # Hammer pattern 116 | if self._is_hammer(data.iloc[i]): 117 | patterns['hammer'].append(i) 118 | 119 | # Doji pattern 120 | if self._is_doji(data.iloc[i]): 121 | patterns['doji'].append(i) 122 | 123 | # Engulfing patterns (need at least 2 candles) 124 | if i > 0: 125 | if self._is_bullish_engulfing(data.iloc[i-1], data.iloc[i]): 126 | patterns['engulfing_bullish'].append(i) 127 | 128 | if self._is_bearish_engulfing(data.iloc[i-1], data.iloc[i]): 129 | patterns['engulfing_bearish'].append(i) 130 | 131 | # Star patterns (need at least 3 candles) 132 | if i > 1: 133 | if self._is_morning_star(data.iloc[i-2:i+1]): 134 | patterns['morning_star'].append(i) 135 | 136 | if self._is_evening_star(data.iloc[i-2:i+1]): 137 | patterns['evening_star'].append(i) 138 | 139 | return patterns 140 | 141 | def _is_hammer(self, candle) -> bool: 142 | body = abs(candle['close'] - candle['open']) 143 | lower_shadow = min(candle['open'], candle['close']) - candle['low'] 144 | upper_shadow = candle['high'] - max(candle['open'], candle['close']) 145 | 146 | return (lower_shadow > 2 * body and upper_shadow < 0.3 * body) 147 | 148 | def _is_doji(self, candle) -> bool: 149 | body = abs(candle['close'] - candle['open']) 150 | range_size = candle['high'] - candle['low'] 151 | 152 | return body < 0.1 * range_size 153 | 154 | def _is_bullish_engulfing(self, prev_candle, curr_candle) -> bool: 155 | prev_bearish = prev_candle['close'] < prev_candle['open'] 156 | curr_bullish = curr_candle['close'] > curr_candle['open'] 157 | 158 | engulfs = (curr_candle['open'] < prev_candle['close'] and 159 | curr_candle['close'] > prev_candle['open']) 160 | 161 | return prev_bearish and curr_bullish and engulfs 162 | 163 | def _is_bearish_engulfing(self, prev_candle, curr_candle) -> bool: 164 | prev_bullish = prev_candle['close'] > prev_candle['open'] 165 | curr_bearish = curr_candle['close'] < curr_candle['open'] 166 | 167 | engulfs = (curr_candle['open'] > prev_candle['close'] and 168 | curr_candle['close'] < prev_candle['open']) 169 | 170 | return prev_bullish and curr_bearish and engulfs 171 | 172 | def _is_morning_star(self, candles) -> bool: 173 | if len(candles) < 3: 174 | return False 175 | 176 | first = candles.iloc[0] 177 | second = candles.iloc[1] 178 | third = candles.iloc[2] 179 | 180 | first_bearish = first['close'] < first['open'] 181 | third_bullish = third['close'] > third['open'] 182 | 183 | second_small_body = abs(second['close'] - second['open']) < 0.3 * abs(first['close'] - first['open']) 184 | gap_down = second['high'] < first['close'] 185 | gap_up = third['open'] > second['high'] 186 | 187 | return first_bearish and third_bullish and second_small_body and gap_down and gap_up 188 | 189 | def _is_evening_star(self, candles) -> bool: 190 | if len(candles) < 3: 191 | return False 192 | 193 | first = candles.iloc[0] 194 | second = candles.iloc[1] 195 | third = candles.iloc[2] 196 | 197 | first_bullish = first['close'] > first['open'] 198 | third_bearish = third['close'] < third['open'] 199 | 200 | second_small_body = abs(second['close'] - second['open']) < 0.3 * abs(first['close'] - first['open']) 201 | gap_up = second['low'] > first['close'] 202 | gap_down = third['open'] < second['low'] 203 | 204 | return first_bullish and third_bearish and second_small_body and gap_up and gap_down 205 | 206 | def calculate_support_resistance(self, data: pd.DataFrame, window: int = 20) -> Dict[str, List[float]]: 207 | highs = data['high'].rolling(window=window).max() 208 | lows = data['low'].rolling(window=window).min() 209 | 210 | resistance_levels = [] 211 | support_levels = [] 212 | 213 | for i in range(window, len(data)): 214 | if data['high'].iloc[i] == highs.iloc[i]: 215 | resistance_levels.append(data['high'].iloc[i]) 216 | 217 | if data['low'].iloc[i] == lows.iloc[i]: 218 | support_levels.append(data['low'].iloc[i]) 219 | 220 | # Remove duplicates and sort 221 | resistance_levels = sorted(list(set(resistance_levels)), reverse=True)[:5] 222 | support_levels = sorted(list(set(support_levels)))[:5] 223 | 224 | return { 225 | 'resistance': resistance_levels, 226 | 'support': support_levels 227 | } 228 | 229 | def trend_analysis(self, data: pd.DataFrame) -> Dict[str, any]: 230 | close_prices = data['close'] 231 | 232 | # Calculate various moving averages 233 | sma_20 = self.sma(close_prices, 20) 234 | sma_50 = self.sma(close_prices, 50) 235 | sma_200 = self.sma(close_prices, 200) 236 | 237 | current_price = close_prices.iloc[-1] 238 | 239 | # Determine trend based on moving averages 240 | short_term_trend = 'bullish' if current_price > sma_20.iloc[-1] else 'bearish' 241 | medium_term_trend = 'bullish' if current_price > sma_50.iloc[-1] else 'bearish' 242 | long_term_trend = 'bullish' if current_price > sma_200.iloc[-1] else 'bearish' 243 | 244 | # Calculate trend strength 245 | rsi_val = self.rsi(close_prices).iloc[-1] 246 | macd_line, _, _ = self.macd(close_prices) 247 | 248 | trend_strength = 'strong' if abs(rsi_val - 50) > 20 else 'weak' 249 | 250 | return { 251 | 'current_price': current_price, 252 | 'short_term_trend': short_term_trend, 253 | 'medium_term_trend': medium_term_trend, 254 | 'long_term_trend': long_term_trend, 255 | 'trend_strength': trend_strength, 256 | 'rsi': rsi_val, 257 | 'macd': macd_line.iloc[-1], 258 | 'sma_20': sma_20.iloc[-1], 259 | 'sma_50': sma_50.iloc[-1], 260 | 'sma_200': sma_200.iloc[-1] 261 | } -------------------------------------------------------------------------------- /src/agent/llm_providers.py: -------------------------------------------------------------------------------- 1 | """ 2 | LLM Provider Interfaces for Multiple AI Services 3 | 4 | Supports OpenAI, DeepSeek, Claude, Moonshot, and other providers 5 | """ 6 | 7 | import os 8 | import requests 9 | import json 10 | from abc import ABC, abstractmethod 11 | from typing import Dict, List, Any, Optional 12 | import logging 13 | 14 | class BaseLLMProvider(ABC): 15 | """Base class for all LLM providers""" 16 | 17 | def __init__(self, api_key: str, model: str = None): 18 | self.api_key = api_key 19 | self.model = model 20 | self.logger = logging.getLogger(__name__) 21 | 22 | @abstractmethod 23 | def generate_response(self, prompt: str, context: Dict[str, Any] = None) -> str: 24 | """Generate response from the LLM""" 25 | pass 26 | 27 | @abstractmethod 28 | def analyze_financial_data(self, data: Dict[str, Any]) -> str: 29 | """Analyze financial data and provide insights""" 30 | pass 31 | 32 | class OpenAIProvider(BaseLLMProvider): 33 | """OpenAI GPT provider""" 34 | 35 | def __init__(self, api_key: str = None, model: str = "gpt-4"): 36 | super().__init__(api_key or os.getenv('OPENAI_API_KEY'), model) 37 | self.base_url = "https://api.openai.com/v1" 38 | 39 | def generate_response(self, prompt: str, context: Dict[str, Any] = None) -> str: 40 | """Generate response using OpenAI API""" 41 | if not self.api_key: 42 | raise ValueError("OpenAI API key not provided") 43 | 44 | headers = { 45 | 'Authorization': f'Bearer {self.api_key}', 46 | 'Content-Type': 'application/json' 47 | } 48 | 49 | messages = [{"role": "user", "content": prompt}] 50 | 51 | if context: 52 | system_message = f"Context: {json.dumps(context, indent=2)}" 53 | messages.insert(0, {"role": "system", "content": system_message}) 54 | 55 | data = { 56 | "model": self.model, 57 | "messages": messages, 58 | "max_tokens": 2000, 59 | "temperature": 0.7 60 | } 61 | 62 | try: 63 | response = requests.post( 64 | f"{self.base_url}/chat/completions", 65 | headers=headers, 66 | json=data, 67 | timeout=30 68 | ) 69 | response.raise_for_status() 70 | 71 | result = response.json() 72 | return result['choices'][0]['message']['content'] 73 | 74 | except Exception as e: 75 | self.logger.error(f"OpenAI API error: {e}") 76 | return f"Error generating response: {str(e)}" 77 | 78 | def analyze_financial_data(self, data: Dict[str, Any]) -> str: 79 | """Analyze financial data using OpenAI""" 80 | prompt = f""" 81 | As a financial analyst, analyze the following financial data and provide insights: 82 | 83 | {json.dumps(data, indent=2)} 84 | 85 | Please provide: 86 | 1. Key financial metrics analysis 87 | 2. Market sentiment assessment 88 | 3. Risk factors identification 89 | 4. Investment recommendations 90 | 5. Technical and fundamental outlook 91 | 92 | Format your response as a structured analysis report. 93 | """ 94 | 95 | return self.generate_response(prompt) 96 | 97 | class DeepSeekProvider(BaseLLMProvider): 98 | """DeepSeek AI provider""" 99 | 100 | def __init__(self, api_key: str = None, model: str = "deepseek-chat"): 101 | super().__init__(api_key or os.getenv('DEEPSEEK_API_KEY'), model) 102 | self.base_url = "https://api.deepseek.com/v1" 103 | 104 | def generate_response(self, prompt: str, context: Dict[str, Any] = None) -> str: 105 | """Generate response using DeepSeek API""" 106 | if not self.api_key: 107 | raise ValueError("DeepSeek API key not provided") 108 | 109 | headers = { 110 | 'Authorization': f'Bearer {self.api_key}', 111 | 'Content-Type': 'application/json' 112 | } 113 | 114 | messages = [{"role": "user", "content": prompt}] 115 | 116 | if context: 117 | system_message = f"Context: {json.dumps(context, indent=2)}" 118 | messages.insert(0, {"role": "system", "content": system_message}) 119 | 120 | data = { 121 | "model": self.model, 122 | "messages": messages, 123 | "max_tokens": 2000, 124 | "temperature": 0.7 125 | } 126 | 127 | try: 128 | response = requests.post( 129 | f"{self.base_url}/chat/completions", 130 | headers=headers, 131 | json=data, 132 | timeout=30 133 | ) 134 | response.raise_for_status() 135 | 136 | result = response.json() 137 | return result['choices'][0]['message']['content'] 138 | 139 | except Exception as e: 140 | self.logger.error(f"DeepSeek API error: {e}") 141 | return f"Error generating response: {str(e)}" 142 | 143 | def analyze_financial_data(self, data: Dict[str, Any]) -> str: 144 | """Analyze financial data using DeepSeek""" 145 | prompt = f""" 146 | Analyze the following financial data from a quantitative perspective: 147 | 148 | {json.dumps(data, indent=2)} 149 | 150 | Provide: 151 | 1. Statistical analysis of key metrics 152 | 2. Risk-return profile assessment 153 | 3. Market efficiency indicators 154 | 4. Quantitative trading signals 155 | 5. Mathematical model recommendations 156 | 157 | Focus on data-driven insights and mathematical rigor. 158 | """ 159 | 160 | return self.generate_response(prompt) 161 | 162 | class ClaudeProvider(BaseLLMProvider): 163 | """Anthropic Claude provider""" 164 | 165 | def __init__(self, api_key: str = None, model: str = "claude-3-sonnet-20240229"): 166 | super().__init__(api_key or os.getenv('ANTHROPIC_API_KEY'), model) 167 | self.base_url = "https://api.anthropic.com/v1" 168 | 169 | def generate_response(self, prompt: str, context: Dict[str, Any] = None) -> str: 170 | """Generate response using Claude API""" 171 | if not self.api_key: 172 | raise ValueError("Anthropic API key not provided") 173 | 174 | headers = { 175 | 'x-api-key': self.api_key, 176 | 'Content-Type': 'application/json', 177 | 'anthropic-version': '2023-06-01' 178 | } 179 | 180 | full_prompt = prompt 181 | if context: 182 | full_prompt = f"Context: {json.dumps(context, indent=2)}\n\n{prompt}" 183 | 184 | data = { 185 | "model": self.model, 186 | "max_tokens": 2000, 187 | "messages": [{"role": "user", "content": full_prompt}] 188 | } 189 | 190 | try: 191 | response = requests.post( 192 | f"{self.base_url}/messages", 193 | headers=headers, 194 | json=data, 195 | timeout=30 196 | ) 197 | response.raise_for_status() 198 | 199 | result = response.json() 200 | return result['content'][0]['text'] 201 | 202 | except Exception as e: 203 | self.logger.error(f"Claude API error: {e}") 204 | return f"Error generating response: {str(e)}" 205 | 206 | def analyze_financial_data(self, data: Dict[str, Any]) -> str: 207 | """Analyze financial data using Claude""" 208 | prompt = f""" 209 | Conduct a comprehensive financial analysis of the following data: 210 | 211 | {json.dumps(data, indent=2)} 212 | 213 | Please provide: 214 | 1. Fundamental analysis with key ratios 215 | 2. Risk assessment and volatility analysis 216 | 3. Market positioning and competitive analysis 217 | 4. Economic factor considerations 218 | 5. Strategic investment recommendations 219 | 220 | Provide balanced, nuanced insights with clear reasoning. 221 | """ 222 | 223 | return self.generate_response(prompt) 224 | 225 | class MoonshotProvider(BaseLLMProvider): 226 | """Moonshot AI provider""" 227 | 228 | def __init__(self, api_key: str = None, model: str = "moonshot-v1-8k"): 229 | super().__init__(api_key or os.getenv('MOONSHOT_API_KEY'), model) 230 | self.base_url = "https://api.moonshot.cn/v1" 231 | 232 | def generate_response(self, prompt: str, context: Dict[str, Any] = None) -> str: 233 | """Generate response using Moonshot API""" 234 | if not self.api_key: 235 | raise ValueError("Moonshot API key not provided") 236 | 237 | headers = { 238 | 'Authorization': f'Bearer {self.api_key}', 239 | 'Content-Type': 'application/json' 240 | } 241 | 242 | messages = [{"role": "user", "content": prompt}] 243 | 244 | if context: 245 | system_message = f"Context: {json.dumps(context, indent=2)}" 246 | messages.insert(0, {"role": "system", "content": system_message}) 247 | 248 | data = { 249 | "model": self.model, 250 | "messages": messages, 251 | "max_tokens": 2000, 252 | "temperature": 0.7 253 | } 254 | 255 | try: 256 | response = requests.post( 257 | f"{self.base_url}/chat/completions", 258 | headers=headers, 259 | json=data, 260 | timeout=30 261 | ) 262 | response.raise_for_status() 263 | 264 | result = response.json() 265 | return result['choices'][0]['message']['content'] 266 | 267 | except Exception as e: 268 | self.logger.error(f"Moonshot API error: {e}") 269 | return f"Error generating response: {str(e)}" 270 | 271 | def analyze_financial_data(self, data: Dict[str, Any]) -> str: 272 | """Analyze financial data using Moonshot""" 273 | prompt = f""" 274 | 从中国市场角度分析以下金融数据: 275 | 276 | {json.dumps(data, indent=2)} 277 | 278 | 请提供: 279 | 1. 基本面分析和关键指标 280 | 2. 市场情绪和投资者行为分析 281 | 3. 政策影响和宏观经济因素 282 | 4. 风险评估和投资建议 283 | 5. 与中国市场的关联性分析 284 | 285 | 请用中英文双语提供专业的金融分析报告。 286 | """ 287 | 288 | return self.generate_response(prompt) 289 | 290 | def get_available_providers() -> List[str]: 291 | """Get list of available LLM providers based on API keys""" 292 | providers = [] 293 | 294 | if os.getenv('OPENAI_API_KEY'): 295 | providers.append('openai') 296 | if os.getenv('DEEPSEEK_API_KEY'): 297 | providers.append('deepseek') 298 | if os.getenv('ANTHROPIC_API_KEY'): 299 | providers.append('claude') 300 | if os.getenv('MOONSHOT_API_KEY'): 301 | providers.append('moonshot') 302 | 303 | return providers 304 | 305 | def create_provider(provider_name: str, **kwargs) -> BaseLLMProvider: 306 | """Factory function to create LLM providers""" 307 | providers = { 308 | 'openai': OpenAIProvider, 309 | 'deepseek': DeepSeekProvider, 310 | 'claude': ClaudeProvider, 311 | 'moonshot': MoonshotProvider 312 | } 313 | 314 | if provider_name not in providers: 315 | raise ValueError(f"Unknown provider: {provider_name}") 316 | 317 | return providers[provider_name](**kwargs) --------------------------------------------------------------------------------