mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
Strategy Builder: - Create strategies from natural language - Grid trading strategy - DCA (Dollar Cost Averaging) strategy - Trend following (EMA crossover) strategy - Custom rule-based strategies Strategy Components: - Entry/exit rules with indicators (RSI, EMA, MACD, etc.) - Position sizing (fixed, percent, risk-based, kelly) - Risk management (max drawdown, daily loss limit, cooldown) - Leverage config (fixed, dynamic, per-symbol, per-volatility) - Time-based rules (trading hours, hold time limits) - AI enhancement (confidence threshold, personality) 11 New Tools: - create_strategy - Natural language strategy creation - create_grid_strategy - Grid trading setup - create_dca_strategy - DCA setup - create_trend_strategy - Trend following setup - list_smart_strategies - List all strategies - get_strategy_details - Strategy details - update_strategy - Modify strategy settings - activate_strategy - Start trading - deactivate_strategy - Stop trading - delete_strategy - Remove strategy - get_strategy_templates - Show available templates Total tools now: 24 (13 trading + 11 strategy)
289 lines
9.1 KiB
Go
289 lines
9.1 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()...)
|
||
|
||
// 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)
|
||
}
|