Files
nofx/main.go
T
tinkle-community cb31782be4 refactor: split large files and clean up project structure
- Rename experience/ to telemetry/ for clarity
- Split 15+ large Go files (800-2200 lines) into focused modules:
  kernel/engine.go, backtest/runner.go, market/data.go, store/position.go,
  api/handler_trader.go, trader/auto_trader_grid.go, and 9 exchange traders
- Split frontend monoliths: types.ts, api.ts, AITradersPage.tsx, BacktestPage.tsx
  into domain-specific modules with barrel re-exports
- Remove stale files: screenshots, .yml.old, pyproject.toml
- Remove unused scripts/ and cmd/ directories
- Remove broken/outdated test files (network-dependent, stale expectations)
2026-03-12 12:53:57 +08:00

199 lines
6.2 KiB
Go

package main
import (
"nofx/api"
"nofx/auth"
"nofx/backtest"
"nofx/config"
"nofx/crypto"
"nofx/telemetry"
"nofx/logger"
"nofx/manager"
"nofx/mcp"
_ "nofx/mcp/payment"
_ "nofx/mcp/provider"
"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)
// Create hot-reload channel for Telegram bot; wire it to the API server
// so that POST /api/telegram can trigger a bot restart when the token changes.
telegramReloadCh := make(chan struct{}, 1)
server.SetTelegramReloadCh(telegramReloadCh)
go func() {
if err := server.Start(); err != nil {
logger.Fatalf("❌ Failed to start API server: %v", err)
}
}()
// Start Telegram bot (if TELEGRAM_BOT_TOKEN is configured)
go telegram.Start(cfg, st, telegramReloadCh)
// 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 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.NewAIClientByProvider("deepseek")
}
// 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
telemetry.SetInstallationID(installationID)
}