package main import ( "nofx/api" "nofx/assistant" "nofx/auth" "nofx/backtest" "nofx/config" "nofx/crypto" "nofx/experience" "nofx/logger" "nofx/manager" "nofx/mcp" "nofx/store" "nofx/telegram" "os" "os/signal" "path/filepath" "syscall" "github.com/google/uuid" "github.com/joho/godotenv" ) func main() { // Load .env environment variables _ = godotenv.Load() // Initialize logger logger.Init(nil) logger.Info("╔════════════════════════════════════════════════════════════╗") logger.Info("║ 🚀 NOFX - AI-Powered Trading System ║") logger.Info("╚════════════════════════════════════════════════════════════╝") // Initialize global configuration (loaded from .env) config.Init() cfg := config.Get() logger.Info("✅ Configuration loaded") // Initialize encryption service BEFORE database (so EncryptedString can decrypt on read) logger.Info("🔐 Initializing encryption service...") cryptoService, err := crypto.NewCryptoService() if err != nil { logger.Fatalf("❌ Failed to initialize encryption service: %v", err) } crypto.SetGlobalCryptoService(cryptoService) logger.Info("✅ Encryption service initialized successfully") // Initialize database from configuration // For backward compatibility: command line arg overrides config (SQLite only) if len(os.Args) > 1 { cfg.DBPath = os.Args[1] } // Ensure data directory exists (for SQLite) if cfg.DBType == "sqlite" { if dir := filepath.Dir(cfg.DBPath); dir != "." { if err := os.MkdirAll(dir, 0755); err != nil { logger.Errorf("Failed to create data directory: %v", err) } } } logger.Infof("📋 Initializing database (%s)...", cfg.DBType) dbType := store.DBTypeSQLite if cfg.DBType == "postgres" { dbType = store.DBTypePostgres } st, err := store.NewWithConfig(store.DBConfig{ Type: dbType, Path: cfg.DBPath, Host: cfg.DBHost, Port: cfg.DBPort, User: cfg.DBUser, Password: cfg.DBPassword, DBName: cfg.DBName, SSLMode: cfg.DBSSLMode, }) if err != nil { logger.Fatalf("❌ Failed to initialize database: %v", err) } defer st.Close() backtest.UseDatabaseWithType(st.DB(), st.DBType() == store.DBTypePostgres) // Initialize installation ID for experience improvement (anonymous statistics) initInstallationID(st) // Set JWT secret auth.SetJWTSecret(cfg.JWTSecret) logger.Info("🔑 JWT secret configured") // WebSocket market monitor is NO LONGER USED // All K-line data now comes from CoinAnk API instead of Binance WebSocket cache // Commented out to reduce unnecessary connections: // go market.NewWSMonitor(150).Start(nil) // logger.Info("📊 WebSocket market monitor started") // time.Sleep(500 * time.Millisecond) logger.Info("📊 Using CoinAnk API for all market data (WebSocket cache disabled)") // Create TraderManager and BacktestManager traderManager := manager.NewTraderManager() mcpClient := newSharedMCPClient() backtestManager := backtest.NewManager(mcpClient) if err := backtestManager.RestoreRuns(); err != nil { logger.Warnf("⚠️ Failed to restore backtest history: %v", err) } // Load all traders from database to memory (may auto-start traders with IsRunning=true) if err := traderManager.LoadTradersFromStore(st); err != nil { logger.Fatalf("❌ Failed to load traders: %v", err) } // Display loaded trader information traders, err := st.Trader().List("default") if err != nil { logger.Fatalf("❌ Failed to get trader list: %v", err) } logger.Info("🤖 AI Trader Configurations in Database:") if len(traders) == 0 { logger.Info(" (No trader configurations, please create via Web interface)") } else { for _, t := range traders { status := "❌ Stopped" if t.IsRunning { status = "✅ Running" } logger.Infof(" • %s [%s] %s - AI Model: %s, Exchange: %s", t.Name, t.ID[:8], status, t.AIModelID, t.ExchangeID) } } // Start API server server := api.NewServer(traderManager, st, cryptoService, backtestManager, cfg.APIServerPort) go func() { if err := server.Start(); err != nil { logger.Fatalf("❌ Failed to start API server: %v", err) } }() // Initialize and start Telegram bot (if configured) var telegramBot *telegram.Bot telegramConfig := telegram.LoadConfigFromEnv() if telegramConfig.Token != "" { logger.Info("🤖 Initializing Smart Trading Assistant...") // Create AI client for the assistant aiClient := createAssistantAIClient() if aiClient == nil { logger.Error("❌ No AI API key configured, Telegram bot disabled") } else { // Create Smart AI Agent with trading context awareness agentConfig := assistant.DefaultAgentConfig() smartAgent := assistant.NewSmartAgent(aiClient, agentConfig, traderManager, st) // Register trading tools tradingTools := assistant.NewTradingTools(traderManager, st) smartAgent.RegisterTools(tradingTools.GetAllTools()...) // Register strategy tools strategyTools := assistant.NewStrategyTools(st) smartAgent.RegisterTools(strategyTools.GetAllTools()...) // Create and start Telegram bot var err error telegramBot, err = telegram.NewBot(telegramConfig, smartAgent.Agent) if err != nil { logger.Errorf("❌ Failed to create Telegram bot: %v", err) } else { // Start background monitor with alert forwarding to Telegram smartAgent.OnAlert(func(alert assistant.Alert) { telegramBot.BroadcastAlert(alert.Message) }) smartAgent.StartMonitor() go telegramBot.Start() logger.Info("✅ Smart Trading Assistant started successfully") logger.Info(" 📊 Real-time context injection: enabled") logger.Info(" 🔍 Background monitoring: enabled") logger.Info(" ⚠️ Proactive alerts: enabled") } } } else { logger.Info("ℹ️ Telegram bot not configured (set TELEGRAM_BOT_TOKEN to enable)") } // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) logger.Info("✅ System started successfully, waiting for trading commands...") logger.Info("📌 Tip: Use Ctrl+C to stop the system") <-quit logger.Info("📴 Shutdown signal received, closing system...") // Stop Telegram bot if telegramBot != nil { telegramBot.Stop() } // Stop all traders traderManager.StopAll() logger.Info("✅ System shut down safely") } // newSharedMCPClient creates a shared MCP AI client (for backtesting) func newSharedMCPClient() mcp.AIClient { apiKey := os.Getenv("DEEPSEEK_API_KEY") if apiKey == "" { logger.Warn("⚠️ DEEPSEEK_API_KEY not set, AI features will be unavailable") return nil } return mcp.NewDeepSeekClient() } // createAssistantAIClient creates an AI client for the Telegram assistant // Supports multiple providers based on environment configuration func createAssistantAIClient() mcp.AIClient { // Try different providers in order of preference // 1. DeepSeek (cost-effective, recommended) if apiKey := os.Getenv("DEEPSEEK_API_KEY"); apiKey != "" { client := mcp.NewDeepSeekClient() customURL := os.Getenv("DEEPSEEK_API_URL") customModel := os.Getenv("DEEPSEEK_MODEL") client.SetAPIKey(apiKey, customURL, customModel) logger.Info("🧠 Assistant using DeepSeek AI") return client } // 2. Claude if apiKey := os.Getenv("CLAUDE_API_KEY"); apiKey != "" { client := mcp.NewClaudeClient() customURL := os.Getenv("CLAUDE_API_URL") customModel := os.Getenv("CLAUDE_MODEL") client.SetAPIKey(apiKey, customURL, customModel) logger.Info("🧠 Assistant using Claude AI") return client } // 3. OpenAI if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" { client := mcp.NewOpenAIClient() customURL := os.Getenv("OPENAI_API_URL") customModel := os.Getenv("OPENAI_MODEL") client.SetAPIKey(apiKey, customURL, customModel) logger.Info("🧠 Assistant using OpenAI") return client } // 4. Qwen if apiKey := os.Getenv("QWEN_API_KEY"); apiKey != "" { client := mcp.NewQwenClient() customURL := os.Getenv("QWEN_API_URL") customModel := os.Getenv("QWEN_MODEL") client.SetAPIKey(apiKey, customURL, customModel) logger.Info("🧠 Assistant using Qwen AI") return client } logger.Warn("⚠️ No AI API key configured for assistant") return nil } // initInstallationID initializes the anonymous installation ID for experience improvement // This ID is persisted in database and used for anonymous usage statistics func initInstallationID(st *store.Store) { const key = "installation_id" // Try to load from database installationID, err := st.GetSystemConfig(key) if err != nil { logger.Warnf("⚠️ Failed to load installation ID: %v", err) } // Generate new ID if not exists if installationID == "" { installationID = uuid.New().String() if err := st.SetSystemConfig(key, installationID); err != nil { logger.Warnf("⚠️ Failed to save installation ID: %v", err) } logger.Infof("📊 Generated new installation ID: %s", installationID[:8]+"...") } // Set installation ID in experience module experience.SetInstallationID(installationID) }