mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 16:26:57 +08:00
feat(nofxi): Phase 3 complete - Exchange factory, Web UI, Strategy runner, Docker
Exchange Factory: - CreateTrader() supports Binance/OKX/Bybit/Bitget/KuCoin/Gate - Auto-registers traders from config on startup - Direct import of nofx/trader packages (merged into main module) Web UI: - Dark theme chat interface at :8900 - Quick action sidebar (analyze, watch, positions, balance) - Real-time health check indicator - Mobile responsive Strategy Runner: - /strategy start BTC 1h - AI auto-analyzes on interval - /strategy stop <id> - stop strategy - /strategy list - view active strategies - Notifications pushed to Telegram on signals - Configurable intervals: 15m/30m/1h/4h Docker: - Multi-stage Dockerfile (alpine, ~20MB) - docker-compose.yml with volume persistence Bug fixes: - Fixed panic on empty exchanges config - Fixed thinking mode response parsing (qwen3 content:null) - Added request timeout (55s) in Telegram handler - Better error logging
This commit is contained in:
@@ -17,10 +17,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/NoFxAiOS/nofx/nofxi/internal/execution"
|
||||
"github.com/NoFxAiOS/nofx/nofxi/internal/memory"
|
||||
"github.com/NoFxAiOS/nofx/nofxi/internal/perception"
|
||||
"github.com/NoFxAiOS/nofx/nofxi/internal/thinking"
|
||||
"nofx/nofxi/internal/execution"
|
||||
"nofx/nofxi/internal/memory"
|
||||
"nofx/nofxi/internal/perception"
|
||||
"nofx/nofxi/internal/thinking"
|
||||
)
|
||||
|
||||
// Agent is the NOFXi agent core.
|
||||
@@ -34,6 +34,9 @@ type Agent struct {
|
||||
|
||||
// NotifyFunc sends proactive notifications to users (set by interaction layer)
|
||||
NotifyFunc func(userID int64, text string) error
|
||||
|
||||
// Strategy runner
|
||||
strategyRunner *StrategyRunner
|
||||
}
|
||||
|
||||
// New creates a new Agent with the given config.
|
||||
@@ -44,6 +47,7 @@ func New(cfg *Config, mem *memory.Store, thinker thinking.Engine, logger *slog.L
|
||||
thinker: thinker,
|
||||
logger: logger,
|
||||
}
|
||||
a.strategyRunner = NewStrategyRunner(a, logger)
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -86,6 +90,8 @@ func (a *Agent) HandleMessage(ctx context.Context, userID int64, text string) (s
|
||||
response, err = a.handleTrade(ctx, userID, intent)
|
||||
case IntentWatch:
|
||||
response = a.HandleWatchCommand(intent.Raw)
|
||||
case IntentStrategy:
|
||||
response = a.handleStrategyCommand(intent.Raw)
|
||||
case IntentSettings:
|
||||
response = a.handleSettings(intent)
|
||||
default:
|
||||
@@ -369,6 +375,9 @@ func (a *Agent) handleTrade(ctx context.Context, userID int64, intent Intent) (s
|
||||
}
|
||||
|
||||
// Use first configured exchange
|
||||
if len(a.config.Exchanges) == 0 {
|
||||
return "⚠️ 还没有配置交易所。请在 config.yaml 的 exchanges 中添加交易所 API Key。", nil
|
||||
}
|
||||
exchange := a.config.Exchanges[0].Name
|
||||
|
||||
// Confirm with user before executing
|
||||
@@ -437,6 +446,64 @@ func (a *Agent) ExecutePendingTrade(ctx context.Context, userID int64) (string,
|
||||
side, symbol, quantity, leverage, exchange, result), nil
|
||||
}
|
||||
|
||||
func (a *Agent) handleStrategyCommand(text string) string {
|
||||
parts := strings.Fields(text)
|
||||
if len(parts) < 2 {
|
||||
return a.strategyRunner.FormatStrategyList()
|
||||
}
|
||||
|
||||
subcmd := strings.ToLower(parts[1])
|
||||
switch subcmd {
|
||||
case "list":
|
||||
return a.strategyRunner.FormatStrategyList()
|
||||
case "start":
|
||||
if len(parts) < 3 {
|
||||
return "Usage: `/strategy start BTC 1h` or `/strategy start ETH 4h binance`"
|
||||
}
|
||||
symbol := strings.ToUpper(parts[2])
|
||||
if !strings.HasSuffix(symbol, "USDT") {
|
||||
symbol += "USDT"
|
||||
}
|
||||
interval := 1 * time.Hour
|
||||
if len(parts) >= 4 {
|
||||
switch parts[3] {
|
||||
case "15m":
|
||||
interval = 15 * time.Minute
|
||||
case "30m":
|
||||
interval = 30 * time.Minute
|
||||
case "1h":
|
||||
interval = 1 * time.Hour
|
||||
case "4h":
|
||||
interval = 4 * time.Hour
|
||||
}
|
||||
}
|
||||
exchange := "binance"
|
||||
if len(parts) >= 5 {
|
||||
exchange = parts[4]
|
||||
}
|
||||
name := fmt.Sprintf("AI-%s", symbol)
|
||||
id, err := a.strategyRunner.StartStrategy(name, symbol, exchange, interval)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("⚠️ %v", err)
|
||||
}
|
||||
return fmt.Sprintf("🚀 Strategy started!\n\n• ID: `%s`\n• Symbol: %s\n• Interval: %s\n• Exchange: %s\n\nStop with: `/strategy stop %s`",
|
||||
id, symbol, interval, exchange, id)
|
||||
case "stop":
|
||||
if len(parts) < 3 {
|
||||
return "Usage: `/strategy stop <id>`"
|
||||
}
|
||||
if err := a.strategyRunner.StopStrategy(parts[2]); err != nil {
|
||||
return fmt.Sprintf("⚠️ %v", err)
|
||||
}
|
||||
return "✅ Strategy stopped."
|
||||
case "stopall":
|
||||
a.strategyRunner.StopAll()
|
||||
return "✅ All strategies stopped."
|
||||
default:
|
||||
return "Unknown subcommand. Use: `/strategy list|start|stop|stopall`"
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) handleSettings(intent Intent) string {
|
||||
return `⚙️ *Settings*
|
||||
|
||||
|
||||
Reference in New Issue
Block a user