diff --git a/assistant/strategy_builder.go b/assistant/strategy_builder.go new file mode 100644 index 00000000..eda01290 --- /dev/null +++ b/assistant/strategy_builder.go @@ -0,0 +1,382 @@ +// Package assistant - Intelligent Strategy Builder +// Allows users to create powerful, flexible trading strategies through natural language +package assistant + +import ( + "fmt" + "nofx/store" + "strings" + "time" + + "github.com/google/uuid" +) + +// StrategyType defines the type of trading strategy +type StrategyType string + +const ( + StrategyTypeAI StrategyType = "ai" // AI decides everything + StrategyTypeTrend StrategyType = "trend" // Trend following + StrategyTypeMeanRevert StrategyType = "mean_revert" // Mean reversion + StrategyTypeGrid StrategyType = "grid" // Grid trading + StrategyTypeDCA StrategyType = "dca" // Dollar cost averaging + StrategyTypeBreakout StrategyType = "breakout" // Breakout trading + StrategyTypeArbitrage StrategyType = "arbitrage" // Cross-exchange arbitrage + StrategyTypeCustom StrategyType = "custom" // Custom rules +) + +// SmartStrategy represents a user-defined trading strategy +type SmartStrategy struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Type StrategyType `json:"type"` + + // Trading pairs + Symbols []string `json:"symbols"` // e.g., ["BTCUSDT", "ETHUSDT"] + SymbolMode string `json:"symbol_mode"` // "static", "ai_select", "top_volume", "top_oi" + MaxSymbols int `json:"max_symbols"` // Max symbols to trade simultaneously + + // Entry conditions + EntryRules []Rule `json:"entry_rules"` + EntryMode string `json:"entry_mode"` // "any" (OR) or "all" (AND) + + // Exit conditions + ExitRules []Rule `json:"exit_rules"` + TakeProfit *float64 `json:"take_profit"` // TP percentage + StopLoss *float64 `json:"stop_loss"` // SL percentage + TrailingStop *float64 `json:"trailing_stop"` // Trailing stop percentage + + // Position sizing + PositionSize PositionSizeConfig `json:"position_size"` + MaxPositions int `json:"max_positions"` // Max concurrent positions + MaxPerSymbol int `json:"max_per_symbol"` // Max positions per symbol + + // Risk management + RiskConfig RiskConfig `json:"risk_config"` + + // Leverage settings + LeverageConfig LeverageConfig `json:"leverage_config"` + + // Time settings + TimeConfig TimeConfig `json:"time_config"` + + // AI enhancement + AIConfig AIStrategyConfig `json:"ai_config"` + + // Metadata + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy string `json:"created_by"` + IsActive bool `json:"is_active"` + Performance *StrategyPerformance `json:"performance,omitempty"` +} + +// Rule represents a trading rule/condition +type Rule struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` // "indicator", "price", "time", "volume", "ai", "custom" + Indicator string `json:"indicator"` // e.g., "RSI", "MACD", "EMA" + Condition string `json:"condition"` // e.g., "crosses_above", "greater_than", "less_than" + Value interface{} `json:"value"` // The value to compare against + Timeframe string `json:"timeframe"` // e.g., "1h", "4h", "1d" + Weight float64 `json:"weight"` // Weight for scoring (0-1) + Description string `json:"description"` // Human readable description +} + +// PositionSizeConfig defines how to size positions +type PositionSizeConfig struct { + Mode string `json:"mode"` // "fixed", "percent", "risk_based", "kelly" + FixedAmount float64 `json:"fixed_amount"` // Fixed USDT amount + PercentOfEquity float64 `json:"percent_of_equity"` // Percentage of total equity + RiskPerTrade float64 `json:"risk_per_trade"` // Max risk per trade (%) + MaxSingleTrade float64 `json:"max_single_trade"` // Max single trade size (USDT) +} + +// RiskConfig defines risk management rules +type RiskConfig struct { + MaxDrawdown float64 `json:"max_drawdown"` // Max drawdown before stopping (%) + MaxDailyLoss float64 `json:"max_daily_loss"` // Max daily loss (%) + MaxOpenRisk float64 `json:"max_open_risk"` // Max total open risk (%) + CooldownAfterLoss int `json:"cooldown_after_loss"` // Minutes to wait after a loss + RequireConfirmation bool `json:"require_confirmation"` // Require user confirmation for trades + EmergencyStopLoss float64 `json:"emergency_stop_loss"` // Emergency SL for all positions (%) +} + +// LeverageConfig defines leverage settings +type LeverageConfig struct { + Mode string `json:"mode"` // "fixed", "dynamic", "per_symbol" + DefaultLeverage int `json:"default_leverage"` + MaxLeverage int `json:"max_leverage"` + PerSymbol map[string]int `json:"per_symbol"` // Symbol-specific leverage + PerVolatility []VolatilityLever `json:"per_volatility"` // Volatility-based leverage +} + +// VolatilityLever defines leverage based on volatility +type VolatilityLever struct { + MaxVolatility float64 `json:"max_volatility"` // ATR percentage threshold + Leverage int `json:"leverage"` +} + +// TimeConfig defines time-based settings +type TimeConfig struct { + TradingHours []TimeRange `json:"trading_hours"` // When to trade + AvoidNews bool `json:"avoid_news"` // Avoid major news events + AvoidWeekends bool `json:"avoid_weekends"` + MinHoldTime int `json:"min_hold_time"` // Minimum hold time (minutes) + MaxHoldTime int `json:"max_hold_time"` // Maximum hold time (minutes) + ScanInterval int `json:"scan_interval"` // How often to scan (minutes) +} + +// TimeRange represents a time range +type TimeRange struct { + Start string `json:"start"` // "09:00" + End string `json:"end"` // "17:00" + TZ string `json:"tz"` // Timezone +} + +// AIStrategyConfig defines AI-specific settings +type AIStrategyConfig struct { + Enabled bool `json:"enabled"` + Model string `json:"model"` // AI model to use + ConfidenceThreshold float64 `json:"confidence_threshold"` // Min confidence to act + UseMarketSentiment bool `json:"use_market_sentiment"` + UseTechnicalAnalysis bool `json:"use_technical_analysis"` + UseOnChainData bool `json:"use_onchain_data"` + CustomPrompt string `json:"custom_prompt"` // Custom instructions for AI + Personality string `json:"personality"` // "aggressive", "conservative", "balanced" +} + +// StrategyPerformance tracks strategy performance +type StrategyPerformance struct { + TotalTrades int `json:"total_trades"` + WinningTrades int `json:"winning_trades"` + LosingTrades int `json:"losing_trades"` + WinRate float64 `json:"win_rate"` + TotalPnL float64 `json:"total_pnl"` + MaxDrawdown float64 `json:"max_drawdown"` + SharpeRatio float64 `json:"sharpe_ratio"` + ProfitFactor float64 `json:"profit_factor"` + AvgWin float64 `json:"avg_win"` + AvgLoss float64 `json:"avg_loss"` + LastUpdated time.Time `json:"last_updated"` +} + +// StrategyBuilder helps users create strategies through conversation +type StrategyBuilder struct { + store *store.Store +} + +// NewStrategyBuilder creates a new strategy builder +func NewStrategyBuilder(st *store.Store) *StrategyBuilder { + return &StrategyBuilder{store: st} +} + +// CreateFromNaturalLanguage creates a strategy from natural language description +func (sb *StrategyBuilder) CreateFromNaturalLanguage(description string, userID string) (*SmartStrategy, error) { + // This would typically call an AI to parse the description + // For now, we create a basic template + strategy := &SmartStrategy{ + ID: uuid.New().String()[:8], + Name: "Custom Strategy", + Description: description, + Type: StrategyTypeAI, + SymbolMode: "ai_select", + MaxSymbols: 5, + EntryMode: "all", + MaxPositions: 5, + MaxPerSymbol: 1, + PositionSize: PositionSizeConfig{ + Mode: "percent", + PercentOfEquity: 5, + MaxSingleTrade: 1000, + }, + RiskConfig: RiskConfig{ + MaxDrawdown: 20, + MaxDailyLoss: 5, + MaxOpenRisk: 10, + CooldownAfterLoss: 30, + RequireConfirmation: true, + EmergencyStopLoss: 30, + }, + LeverageConfig: LeverageConfig{ + Mode: "dynamic", + DefaultLeverage: 3, + MaxLeverage: 10, + }, + TimeConfig: TimeConfig{ + ScanInterval: 5, + AvoidWeekends: false, + }, + AIConfig: AIStrategyConfig{ + Enabled: true, + ConfidenceThreshold: 0.7, + UseMarketSentiment: true, + UseTechnicalAnalysis: true, + Personality: "balanced", + CustomPrompt: description, + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + IsActive: false, + } + + return strategy, nil +} + +// CreateGridStrategy creates a grid trading strategy +func (sb *StrategyBuilder) CreateGridStrategy(symbol string, lowerPrice, upperPrice float64, gridCount int, amountPerGrid float64) *SmartStrategy { + return &SmartStrategy{ + ID: uuid.New().String()[:8], + Name: fmt.Sprintf("Grid %s", symbol), + Description: fmt.Sprintf("Grid trading %s from %.2f to %.2f with %d grids", symbol, lowerPrice, upperPrice, gridCount), + Type: StrategyTypeGrid, + Symbols: []string{symbol}, + SymbolMode: "static", + MaxPositions: gridCount, + PositionSize: PositionSizeConfig{ + Mode: "fixed", + FixedAmount: amountPerGrid, + }, + EntryRules: []Rule{ + { + ID: "grid_entry", + Type: "price", + Condition: "grid_level", + Value: map[string]interface{}{ + "lower_price": lowerPrice, + "upper_price": upperPrice, + "grid_count": gridCount, + }, + }, + }, + CreatedAt: time.Now(), + IsActive: false, + } +} + +// CreateDCAStrategy creates a DCA strategy +func (sb *StrategyBuilder) CreateDCAStrategy(symbol string, intervalMinutes int, amountPerBuy float64, maxBuys int) *SmartStrategy { + return &SmartStrategy{ + ID: uuid.New().String()[:8], + Name: fmt.Sprintf("DCA %s", symbol), + Description: fmt.Sprintf("DCA into %s every %d minutes, $%.2f per buy, max %d buys", symbol, intervalMinutes, amountPerBuy, maxBuys), + Type: StrategyTypeDCA, + Symbols: []string{symbol}, + SymbolMode: "static", + MaxPositions: maxBuys, + PositionSize: PositionSizeConfig{ + Mode: "fixed", + FixedAmount: amountPerBuy, + }, + TimeConfig: TimeConfig{ + ScanInterval: intervalMinutes, + }, + CreatedAt: time.Now(), + IsActive: false, + } +} + +// CreateTrendStrategy creates a trend following strategy +func (sb *StrategyBuilder) CreateTrendStrategy(symbols []string, emaFast, emaSlow int, leverage int) *SmartStrategy { + return &SmartStrategy{ + ID: uuid.New().String()[:8], + Name: "Trend Following", + Description: fmt.Sprintf("EMA %d/%d crossover strategy", emaFast, emaSlow), + Type: StrategyTypeTrend, + Symbols: symbols, + SymbolMode: "static", + EntryMode: "all", + EntryRules: []Rule{ + { + ID: "ema_cross", + Name: "EMA Crossover", + Type: "indicator", + Indicator: "EMA", + Condition: "crosses_above", + Value: map[string]int{ + "fast_period": emaFast, + "slow_period": emaSlow, + }, + Timeframe: "1h", + Weight: 1.0, + }, + }, + ExitRules: []Rule{ + { + ID: "ema_cross_exit", + Name: "EMA Crossover Exit", + Type: "indicator", + Indicator: "EMA", + Condition: "crosses_below", + Value: map[string]int{ + "fast_period": emaFast, + "slow_period": emaSlow, + }, + Timeframe: "1h", + }, + }, + LeverageConfig: LeverageConfig{ + Mode: "fixed", + DefaultLeverage: leverage, + }, + CreatedAt: time.Now(), + IsActive: false, + } +} + +// StrategyToPrompt converts a strategy to an AI prompt +func StrategyToPrompt(s *SmartStrategy) string { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf("# 策略: %s\n\n", s.Name)) + sb.WriteString(fmt.Sprintf("**描述**: %s\n", s.Description)) + sb.WriteString(fmt.Sprintf("**类型**: %s\n\n", s.Type)) + + // Trading pairs + if len(s.Symbols) > 0 { + sb.WriteString(fmt.Sprintf("**交易对**: %s\n", strings.Join(s.Symbols, ", "))) + } else { + sb.WriteString(fmt.Sprintf("**选币模式**: %s (最多 %d 个)\n", s.SymbolMode, s.MaxSymbols)) + } + + // Entry rules + if len(s.EntryRules) > 0 { + sb.WriteString("\n## 入场规则\n") + for _, rule := range s.EntryRules { + sb.WriteString(fmt.Sprintf("- %s: %s %s %v\n", rule.Name, rule.Indicator, rule.Condition, rule.Value)) + } + } + + // Exit rules + sb.WriteString("\n## 出场规则\n") + if s.TakeProfit != nil { + sb.WriteString(fmt.Sprintf("- 止盈: %.1f%%\n", *s.TakeProfit)) + } + if s.StopLoss != nil { + sb.WriteString(fmt.Sprintf("- 止损: %.1f%%\n", *s.StopLoss)) + } + if s.TrailingStop != nil { + sb.WriteString(fmt.Sprintf("- 移动止损: %.1f%%\n", *s.TrailingStop)) + } + + // Risk management + sb.WriteString("\n## 风险管理\n") + sb.WriteString(fmt.Sprintf("- 最大回撤: %.1f%%\n", s.RiskConfig.MaxDrawdown)) + sb.WriteString(fmt.Sprintf("- 单日最大亏损: %.1f%%\n", s.RiskConfig.MaxDailyLoss)) + sb.WriteString(fmt.Sprintf("- 最大持仓数: %d\n", s.MaxPositions)) + + // AI settings + if s.AIConfig.Enabled { + sb.WriteString("\n## AI 配置\n") + sb.WriteString(fmt.Sprintf("- 置信度阈值: %.0f%%\n", s.AIConfig.ConfidenceThreshold*100)) + sb.WriteString(fmt.Sprintf("- 风格: %s\n", s.AIConfig.Personality)) + if s.AIConfig.CustomPrompt != "" { + sb.WriteString(fmt.Sprintf("- 自定义指令: %s\n", s.AIConfig.CustomPrompt)) + } + } + + return sb.String() +} diff --git a/assistant/strategy_tools.go b/assistant/strategy_tools.go new file mode 100644 index 00000000..f93a4f86 --- /dev/null +++ b/assistant/strategy_tools.go @@ -0,0 +1,548 @@ +package assistant + +import ( + "context" + "encoding/json" + "fmt" + "nofx/store" +) + +// StrategyTools provides strategy management tools for the AI agent +type StrategyTools struct { + store *store.Store + strategyBuilder *StrategyBuilder + strategies map[string]*SmartStrategy // In-memory strategy cache +} + +// NewStrategyTools creates strategy tools +func NewStrategyTools(st *store.Store) *StrategyTools { + return &StrategyTools{ + store: st, + strategyBuilder: NewStrategyBuilder(st), + strategies: make(map[string]*SmartStrategy), + } +} + +// GetAllTools returns all strategy tools +func (st *StrategyTools) GetAllTools() []Tool { + return []Tool{ + st.CreateStrategyTool(), + st.CreateGridStrategyTool(), + st.CreateDCAStrategyTool(), + st.CreateTrendStrategyTool(), + st.ListSmartStrategiesTool(), + st.GetStrategyDetailsTool(), + st.UpdateStrategyTool(), + st.ActivateStrategyTool(), + st.DeactivateStrategyTool(), + st.DeleteStrategyTool(), + st.GetStrategyTemplates(), + } +} + +// CreateStrategyTool creates a strategy from natural language +func (st *StrategyTools) CreateStrategyTool() Tool { + return NewTool( + "create_strategy", + `Create a new trading strategy from natural language description. +Examples: +- "当RSI低于30时买入BTC,RSI高于70时卖出" +- "每天定投100美元ETH" +- "BTC在5万到6万之间做网格交易"`, + `{ + "name": "string (required) - Strategy name", + "description": "string (required) - Natural language description of the strategy", + "symbols": "array (optional) - Trading pairs, e.g., [\"BTCUSDT\", \"ETHUSDT\"]", + "take_profit": "number (optional) - Take profit percentage", + "stop_loss": "number (optional) - Stop loss percentage", + "leverage": "number (optional) - Leverage to use (default: 3)", + "max_positions": "number (optional) - Max concurrent positions (default: 5)" + }`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + Name string `json:"name"` + Description string `json:"description"` + Symbols []string `json:"symbols"` + TakeProfit *float64 `json:"take_profit"` + StopLoss *float64 `json:"stop_loss"` + Leverage int `json:"leverage"` + MaxPositions int `json:"max_positions"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if params.Description == "" { + return nil, fmt.Errorf("strategy description is required") + } + + strategy, err := st.strategyBuilder.CreateFromNaturalLanguage(params.Description, "default") + if err != nil { + return nil, err + } + + // Apply user customizations + if params.Name != "" { + strategy.Name = params.Name + } + if len(params.Symbols) > 0 { + strategy.Symbols = params.Symbols + strategy.SymbolMode = "static" + } + if params.TakeProfit != nil { + strategy.TakeProfit = params.TakeProfit + } + if params.StopLoss != nil { + strategy.StopLoss = params.StopLoss + } + if params.Leverage > 0 { + strategy.LeverageConfig.DefaultLeverage = params.Leverage + } + if params.MaxPositions > 0 { + strategy.MaxPositions = params.MaxPositions + } + + // Store in memory + st.strategies[strategy.ID] = strategy + + return map[string]interface{}{ + "success": true, + "strategy": strategy, + "message": fmt.Sprintf("策略 '%s' (ID: %s) 创建成功!使用 activate_strategy 激活它。", strategy.Name, strategy.ID), + }, nil + }, + ) +} + +// CreateGridStrategyTool creates a grid trading strategy +func (st *StrategyTools) CreateGridStrategyTool() Tool { + return NewTool( + "create_grid_strategy", + "Create a grid trading strategy. Grid trading places buy and sell orders at predetermined price levels.", + `{ + "symbol": "string (required) - Trading pair, e.g., BTCUSDT", + "lower_price": "number (required) - Lower price bound", + "upper_price": "number (required) - Upper price bound", + "grid_count": "number (required) - Number of grids (10-100)", + "amount_per_grid": "number (required) - USDT amount per grid" + }`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + Symbol string `json:"symbol"` + LowerPrice float64 `json:"lower_price"` + UpperPrice float64 `json:"upper_price"` + GridCount int `json:"grid_count"` + AmountPerGrid float64 `json:"amount_per_grid"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if params.LowerPrice >= params.UpperPrice { + return nil, fmt.Errorf("lower_price must be less than upper_price") + } + if params.GridCount < 2 || params.GridCount > 100 { + return nil, fmt.Errorf("grid_count must be between 2 and 100") + } + + strategy := st.strategyBuilder.CreateGridStrategy( + params.Symbol, params.LowerPrice, params.UpperPrice, + params.GridCount, params.AmountPerGrid, + ) + st.strategies[strategy.ID] = strategy + + gridSize := (params.UpperPrice - params.LowerPrice) / float64(params.GridCount) + totalInvestment := params.AmountPerGrid * float64(params.GridCount) + + return map[string]interface{}{ + "success": true, + "strategy": strategy, + "details": map[string]interface{}{ + "grid_size": gridSize, + "total_investment": totalInvestment, + "profit_per_grid": (gridSize / params.LowerPrice) * 100, + }, + "message": fmt.Sprintf("网格策略创建成功!\n价格区间: %.2f - %.2f\n网格数: %d\n每格间距: %.2f\n总投资: $%.2f", + params.LowerPrice, params.UpperPrice, params.GridCount, gridSize, totalInvestment), + }, nil + }, + ) +} + +// CreateDCAStrategyTool creates a DCA strategy +func (st *StrategyTools) CreateDCAStrategyTool() Tool { + return NewTool( + "create_dca_strategy", + "Create a Dollar Cost Averaging (DCA) strategy. Automatically buy at regular intervals.", + `{ + "symbol": "string (required) - Trading pair, e.g., BTCUSDT", + "interval_minutes": "number (required) - Buy interval in minutes (min: 5)", + "amount_per_buy": "number (required) - USDT amount per purchase", + "max_buys": "number (optional) - Maximum number of buys (default: unlimited)" + }`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + Symbol string `json:"symbol"` + IntervalMinutes int `json:"interval_minutes"` + AmountPerBuy float64 `json:"amount_per_buy"` + MaxBuys int `json:"max_buys"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if params.IntervalMinutes < 5 { + return nil, fmt.Errorf("interval must be at least 5 minutes") + } + if params.MaxBuys == 0 { + params.MaxBuys = 1000 // Effectively unlimited + } + + strategy := st.strategyBuilder.CreateDCAStrategy( + params.Symbol, params.IntervalMinutes, params.AmountPerBuy, params.MaxBuys, + ) + st.strategies[strategy.ID] = strategy + + return map[string]interface{}{ + "success": true, + "strategy": strategy, + "message": fmt.Sprintf("DCA策略创建成功!\n币种: %s\n定投间隔: %d分钟\n每次金额: $%.2f\n最大次数: %d", + params.Symbol, params.IntervalMinutes, params.AmountPerBuy, params.MaxBuys), + }, nil + }, + ) +} + +// CreateTrendStrategyTool creates a trend following strategy +func (st *StrategyTools) CreateTrendStrategyTool() Tool { + return NewTool( + "create_trend_strategy", + "Create a trend following strategy using EMA crossover.", + `{ + "symbols": "array (required) - Trading pairs", + "ema_fast": "number (optional) - Fast EMA period (default: 9)", + "ema_slow": "number (optional) - Slow EMA period (default: 21)", + "leverage": "number (optional) - Leverage (default: 3)", + "take_profit": "number (optional) - Take profit %", + "stop_loss": "number (optional) - Stop loss %" + }`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + Symbols []string `json:"symbols"` + EMAFast int `json:"ema_fast"` + EMASlow int `json:"ema_slow"` + Leverage int `json:"leverage"` + TakeProfit *float64 `json:"take_profit"` + StopLoss *float64 `json:"stop_loss"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if len(params.Symbols) == 0 { + params.Symbols = []string{"BTCUSDT", "ETHUSDT"} + } + if params.EMAFast == 0 { + params.EMAFast = 9 + } + if params.EMASlow == 0 { + params.EMASlow = 21 + } + if params.Leverage == 0 { + params.Leverage = 3 + } + + strategy := st.strategyBuilder.CreateTrendStrategy( + params.Symbols, params.EMAFast, params.EMASlow, params.Leverage, + ) + strategy.TakeProfit = params.TakeProfit + strategy.StopLoss = params.StopLoss + st.strategies[strategy.ID] = strategy + + return map[string]interface{}{ + "success": true, + "strategy": strategy, + "message": fmt.Sprintf("趋势策略创建成功!\nEMA %d/%d 交叉\n交易对: %v\n杠杆: %dx", + params.EMAFast, params.EMASlow, params.Symbols, params.Leverage), + }, nil + }, + ) +} + +// ListSmartStrategiesTool lists all smart strategies +func (st *StrategyTools) ListSmartStrategiesTool() Tool { + return NewTool( + "list_smart_strategies", + "List all smart strategies (both in-memory and saved).", + `{}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var result []map[string]interface{} + + for _, s := range st.strategies { + result = append(result, map[string]interface{}{ + "id": s.ID, + "name": s.Name, + "type": s.Type, + "description": s.Description, + "is_active": s.IsActive, + "symbols": s.Symbols, + "created_at": s.CreatedAt, + }) + } + + // Also get strategies from store + if dbStrategies, err := st.store.Strategy().List("default"); err == nil { + for _, s := range dbStrategies { + result = append(result, map[string]interface{}{ + "id": s.ID, + "name": s.Name, + "type": "db_strategy", + "description": s.Description, + "is_active": s.IsActive, + "source": "database", + }) + } + } + + if len(result) == 0 { + return map[string]interface{}{ + "strategies": []interface{}{}, + "message": "暂无策略。使用 create_strategy 创建一个新策略。", + }, nil + } + + return map[string]interface{}{ + "strategies": result, + "count": len(result), + }, nil + }, + ) +} + +// GetStrategyDetailsTool gets detailed strategy info +func (st *StrategyTools) GetStrategyDetailsTool() Tool { + return NewTool( + "get_strategy_details", + "Get detailed information about a specific strategy.", + `{"strategy_id": "string (required)"}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + StrategyID string `json:"strategy_id"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if s, ok := st.strategies[params.StrategyID]; ok { + return map[string]interface{}{ + "strategy": s, + "prompt_text": StrategyToPrompt(s), + }, nil + } + + return nil, fmt.Errorf("strategy not found: %s", params.StrategyID) + }, + ) +} + +// UpdateStrategyTool updates a strategy +func (st *StrategyTools) UpdateStrategyTool() Tool { + return NewTool( + "update_strategy", + "Update an existing strategy's settings.", + `{ + "strategy_id": "string (required)", + "name": "string (optional)", + "take_profit": "number (optional)", + "stop_loss": "number (optional)", + "leverage": "number (optional)", + "max_positions": "number (optional)", + "symbols": "array (optional)" + }`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + StrategyID string `json:"strategy_id"` + Name string `json:"name"` + TakeProfit *float64 `json:"take_profit"` + StopLoss *float64 `json:"stop_loss"` + Leverage int `json:"leverage"` + MaxPositions int `json:"max_positions"` + Symbols []string `json:"symbols"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + s, ok := st.strategies[params.StrategyID] + if !ok { + return nil, fmt.Errorf("strategy not found: %s", params.StrategyID) + } + + if params.Name != "" { + s.Name = params.Name + } + if params.TakeProfit != nil { + s.TakeProfit = params.TakeProfit + } + if params.StopLoss != nil { + s.StopLoss = params.StopLoss + } + if params.Leverage > 0 { + s.LeverageConfig.DefaultLeverage = params.Leverage + } + if params.MaxPositions > 0 { + s.MaxPositions = params.MaxPositions + } + if len(params.Symbols) > 0 { + s.Symbols = params.Symbols + } + + return map[string]interface{}{ + "success": true, + "strategy": s, + "message": "策略已更新", + }, nil + }, + ) +} + +// ActivateStrategyTool activates a strategy +func (st *StrategyTools) ActivateStrategyTool() Tool { + return NewTool( + "activate_strategy", + "Activate a strategy to start trading. ⚠️ This will start real trading!", + `{"strategy_id": "string (required)"}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + StrategyID string `json:"strategy_id"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + s, ok := st.strategies[params.StrategyID] + if !ok { + return nil, fmt.Errorf("strategy not found: %s", params.StrategyID) + } + + s.IsActive = true + + return map[string]interface{}{ + "success": true, + "message": fmt.Sprintf("⚠️ 策略 '%s' 已激活!将开始真实交易。", s.Name), + "strategy": s, + }, nil + }, + ) +} + +// DeactivateStrategyTool deactivates a strategy +func (st *StrategyTools) DeactivateStrategyTool() Tool { + return NewTool( + "deactivate_strategy", + "Deactivate a strategy to stop trading.", + `{"strategy_id": "string (required)"}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + StrategyID string `json:"strategy_id"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + s, ok := st.strategies[params.StrategyID] + if !ok { + return nil, fmt.Errorf("strategy not found: %s", params.StrategyID) + } + + s.IsActive = false + + return map[string]interface{}{ + "success": true, + "message": fmt.Sprintf("策略 '%s' 已停用", s.Name), + }, nil + }, + ) +} + +// DeleteStrategyTool deletes a strategy +func (st *StrategyTools) DeleteStrategyTool() Tool { + return NewTool( + "delete_strategy", + "Delete a strategy permanently.", + `{"strategy_id": "string (required)"}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + var params struct { + StrategyID string `json:"strategy_id"` + } + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, fmt.Errorf("invalid arguments: %w", err) + } + + if _, ok := st.strategies[params.StrategyID]; !ok { + return nil, fmt.Errorf("strategy not found: %s", params.StrategyID) + } + + delete(st.strategies, params.StrategyID) + + return map[string]interface{}{ + "success": true, + "message": "策略已删除", + }, nil + }, + ) +} + +// GetStrategyTemplates returns available strategy templates +func (st *StrategyTools) GetStrategyTemplates() Tool { + return NewTool( + "get_strategy_templates", + "Get available strategy templates and examples.", + `{}`, + func(ctx context.Context, args json.RawMessage) (interface{}, error) { + templates := []map[string]interface{}{ + { + "name": "AI 智能交易", + "type": "ai", + "description": "让 AI 自主分析市场并决策,适合不想手动盯盘的用户", + "example": "create_strategy(name='AI智能', description='分析BTC和ETH的技术指标和市场情绪,在有明确趋势时入场')", + }, + { + "name": "网格交易", + "type": "grid", + "description": "在价格区间内自动低买高卖,适合震荡行情", + "example": "create_grid_strategy(symbol='BTCUSDT', lower_price=90000, upper_price=100000, grid_count=20, amount_per_grid=100)", + }, + { + "name": "定投 DCA", + "type": "dca", + "description": "定期定额买入,摊薄成本,适合长期投资", + "example": "create_dca_strategy(symbol='ETHUSDT', interval_minutes=1440, amount_per_buy=50, max_buys=365)", + }, + { + "name": "趋势跟踪", + "type": "trend", + "description": "跟随趋势,EMA金叉买入死叉卖出", + "example": "create_trend_strategy(symbols=['BTCUSDT','ETHUSDT'], ema_fast=9, ema_slow=21, leverage=3)", + }, + { + "name": "RSI 超买超卖", + "type": "custom", + "description": "RSI 低于 30 买入,高于 70 卖出", + "example": "create_strategy(name='RSI策略', description='当RSI14低于30时买入,高于70时卖出,止损10%')", + }, + { + "name": "突破策略", + "type": "breakout", + "description": "价格突破关键位时入场", + "example": "create_strategy(name='突破策略', description='当价格突破20日最高点时做多,突破20日最低点时做空')", + }, + } + + return map[string]interface{}{ + "templates": templates, + "message": "以上是可用的策略模板,选择一个并告诉我你想怎么定制!", + }, nil + }, + ) +} diff --git a/main.go b/main.go index 8bd030c9..e1c8704f 100644 --- a/main.go +++ b/main.go @@ -157,6 +157,10 @@ func main() { 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)