mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 06:20:58 +08:00
- Add SmartAgent with automatic context injection - Real-time portfolio/position data in every prompt - AI knows current state before responding - Add TradingContext builder - Aggregates balance, positions, P&L across all traders - Auto-generates alerts (liquidation risk, large loss, etc.) - Add background Monitor - Proactive position monitoring every 30s - Detects new positions, closed positions - Forwards alerts to Telegram - Enhanced system prompts - Professional trading assistant persona - Risk assessment guidelines - Clear response formatting rules Features: ✅ Context-aware responses ✅ Proactive risk alerts ✅ Background monitoring ✅ Alert broadcasting to Telegram
285 lines
9.0 KiB
Go
285 lines
9.0 KiB
Go
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()...)
|
||
|
||
// 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)
|
||
}
|