Files
nofx/assistant/smart_agent.go
tinklefund f9d8318869 feat: Add smart trading assistant with context awareness
- Add SmartAgent with automatic context injection
  - Real-time portfolio/position data in every prompt
  - AI knows current state before responding

- Add TradingContext builder
  - Aggregates balance, positions, P&L across all traders
  - Auto-generates alerts (liquidation risk, large loss, etc.)

- Add background Monitor
  - Proactive position monitoring every 30s
  - Detects new positions, closed positions
  - Forwards alerts to Telegram

- Enhanced system prompts
  - Professional trading assistant persona
  - Risk assessment guidelines
  - Clear response formatting rules

Features:
 Context-aware responses
 Proactive risk alerts
 Background monitoring
 Alert broadcasting to Telegram
2026-01-30 03:40:14 +08:00

217 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package assistant
import (
"context"
"fmt"
"nofx/logger"
"nofx/manager"
"nofx/mcp"
"nofx/store"
"strings"
"time"
)
// SmartAgent is an enhanced AI agent with trading context awareness
type SmartAgent struct {
*Agent
contextBuilder *ContextBuilder
monitor *Monitor
// Auto-inject context into prompts
autoInjectContext bool
}
// NewSmartAgent creates a new smart trading agent
func NewSmartAgent(aiClient mcp.AIClient, config AgentConfig, tm *manager.TraderManager, st *store.Store) *SmartAgent {
baseAgent := NewAgent(aiClient, config)
baseAgent.SetSystemPrompt(SmartTradingPrompt())
contextBuilder := NewContextBuilder(tm, st)
monitor := NewMonitor(tm, st)
return &SmartAgent{
Agent: baseAgent,
contextBuilder: contextBuilder,
monitor: monitor,
autoInjectContext: true,
}
}
// SetAutoInjectContext enables/disables automatic context injection
func (sa *SmartAgent) SetAutoInjectContext(enabled bool) {
sa.autoInjectContext = enabled
}
// StartMonitor starts the background monitor
func (sa *SmartAgent) StartMonitor() {
sa.monitor.Start()
}
// StopMonitor stops the background monitor
func (sa *SmartAgent) StopMonitor() {
sa.monitor.Stop()
}
// OnAlert registers an alert callback
func (sa *SmartAgent) OnAlert(callback func(Alert)) {
sa.monitor.OnAlert(callback)
}
// Chat processes a message with smart context injection
func (sa *SmartAgent) Chat(ctx context.Context, sessionID string, userMessage string) (*AgentResponse, error) {
session := sa.GetSession(sessionID)
// Add user message to history
session.AddMessage(Message{
Role: "user",
Content: userMessage,
Timestamp: time.Now(),
})
// Build system prompt with tools
systemPrompt := sa.buildSmartSystemPrompt()
// Build conversation prompt with context injection
conversationPrompt := sa.buildSmartConversationPrompt(session, userMessage)
// Agent loop
var finalResponse string
toolCallCount := 0
for {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if toolCallCount >= sa.config.MaxToolCalls {
logger.Warnf("⚠️ Max tool calls reached (%d)", sa.config.MaxToolCalls)
break
}
response, err := sa.aiClient.CallWithMessages(systemPrompt, conversationPrompt)
if err != nil {
return nil, fmt.Errorf("AI call failed: %w", err)
}
toolCalls, textResponse, err := sa.parseResponse(response)
if err != nil {
finalResponse = response
break
}
if len(toolCalls) == 0 {
finalResponse = textResponse
break
}
// Execute tool calls
toolResults := sa.executeToolCalls(ctx, toolCalls)
toolCallCount += len(toolCalls)
// Add results to conversation
conversationPrompt += fmt.Sprintf("\n\nAssistant called tools:\n%s\n\nTool results:\n%s\n\nBased on the tool results, provide a helpful response:",
formatToolCalls(toolCalls),
formatToolResults(toolResults))
if textResponse != "" {
finalResponse = textResponse
}
}
// Add response to history
session.AddMessage(Message{
Role: "assistant",
Content: finalResponse,
Timestamp: time.Now(),
})
return &AgentResponse{
Text: finalResponse,
SessionID: sessionID,
}, nil
}
// buildSmartSystemPrompt builds system prompt with tools
func (sa *SmartAgent) buildSmartSystemPrompt() string {
sa.toolsLock.RLock()
defer sa.toolsLock.RUnlock()
var toolDefs []string
for _, tool := range sa.tools {
toolDef := fmt.Sprintf(`- **%s**: %s
Parameters: %s`, tool.Name(), tool.Description(), tool.ParameterSchema())
toolDefs = append(toolDefs, toolDef)
}
toolsSection := ""
if len(toolDefs) > 0 {
toolsSection = fmt.Sprintf(`
## 可用工具
调用工具时,使用以下 JSON 格式:
{"tool_calls": [{"name": "工具名", "arguments": {"参数": "值"}}]}
收到工具结果后,用自然语言回复用户。
可用工具:
%s
`, strings.Join(toolDefs, "\n"))
}
return sa.systemPrompt + toolsSection
}
// buildSmartConversationPrompt builds conversation with context injection
func (sa *SmartAgent) buildSmartConversationPrompt(session *Session, currentMessage string) string {
var sb strings.Builder
// Inject current trading context if enabled
if sa.autoInjectContext {
tradingCtx := sa.contextBuilder.BuildContext()
sb.WriteString(tradingCtx.FormatContextForPrompt())
}
// Add conversation history
messages := session.GetMessages()
for _, msg := range messages {
sb.WriteString(fmt.Sprintf("\n%s: %s\n", strings.Title(msg.Role), msg.Content))
}
return sb.String()
}
// QuickStatus returns a quick status summary
func (sa *SmartAgent) QuickStatus() string {
ctx := sa.contextBuilder.BuildContext()
var sb strings.Builder
sb.WriteString("📊 **交易状态概览**\n\n")
sb.WriteString(fmt.Sprintf("💰 总权益: $%.2f\n", ctx.TotalEquity))
sb.WriteString(fmt.Sprintf("💵 可用余额: $%.2f\n", ctx.AvailableBalance))
if ctx.UnrealizedPnL >= 0 {
sb.WriteString(fmt.Sprintf("📈 未实现盈亏: 🟢 +$%.2f\n", ctx.UnrealizedPnL))
} else {
sb.WriteString(fmt.Sprintf("📉 未实现盈亏: 🔴 $%.2f\n", ctx.UnrealizedPnL))
}
sb.WriteString(fmt.Sprintf("📍 持仓数: %d\n", len(ctx.Positions)))
sb.WriteString(fmt.Sprintf("🤖 运行交易员: %d\n", len(ctx.ActiveTraders)))
if len(ctx.Alerts) > 0 {
sb.WriteString("\n⚠ **警报**\n")
for _, alert := range ctx.Alerts {
sb.WriteString(fmt.Sprintf("- %s\n", alert.Message))
}
}
return sb.String()
}
// GetTradingContext returns current trading context
func (sa *SmartAgent) GetTradingContext() *TradingContext {
return sa.contextBuilder.BuildContext()
}