mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
feat: Add powerful flexible strategy system
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)
This commit is contained in:
382
assistant/strategy_builder.go
Normal file
382
assistant/strategy_builder.go
Normal file
@@ -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()
|
||||
}
|
||||
548
assistant/strategy_tools.go
Normal file
548
assistant/strategy_tools.go
Normal file
@@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
4
main.go
4
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)
|
||||
|
||||
Reference in New Issue
Block a user