mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 01:14:40 +08:00
feat: init nofxi - AI Trading Agent module
NOFXi — Your AI Trading Agent, built on NOFX. Architecture: - Agent Core: intent routing, conversation memory, trade confirmation - Memory: SQLite (trades, conversations, preferences, strategies) - Thinking: LLM client (OpenAI/claw402/DeepSeek compatible) - Execution: Bridge to NOFX trader engine (9 exchanges) - Perception: Market monitor, price alerts, anomaly detection - Interaction: Telegram bot + REST API + OpenAI-compatible endpoint Features: - Natural language trading (中英文) - /buy /sell /analyze /watch /alert /positions /balance - Daily P/L reports, portfolio risk checks - Trade confirmation flow (safety first) - Price polling and alert notifications
This commit is contained in:
28
nofxi/internal/thinking/engine.go
Normal file
28
nofxi/internal/thinking/engine.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package thinking
|
||||
|
||||
import "context"
|
||||
|
||||
// Engine is the AI decision-making interface.
|
||||
type Engine interface {
|
||||
// Chat sends a message to the LLM with conversation context and returns a response.
|
||||
Chat(ctx context.Context, messages []Message) (string, error)
|
||||
|
||||
// Analyze asks the AI to analyze market data and provide a trading recommendation.
|
||||
Analyze(ctx context.Context, prompt string) (*Analysis, error)
|
||||
}
|
||||
|
||||
// Message represents a chat message.
|
||||
type Message struct {
|
||||
Role string `json:"role"` // "system", "user", "assistant"
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// Analysis holds an AI trading recommendation.
|
||||
type Analysis struct {
|
||||
Action string `json:"action"` // "buy", "sell", "hold", "wait"
|
||||
Symbol string `json:"symbol"`
|
||||
Confidence float64 `json:"confidence"` // 0.0 - 1.0
|
||||
Reasoning string `json:"reasoning"`
|
||||
StopLoss float64 `json:"stop_loss,omitempty"`
|
||||
TakeProfit float64 `json:"take_profit,omitempty"`
|
||||
}
|
||||
144
nofxi/internal/thinking/llm.go
Normal file
144
nofxi/internal/thinking/llm.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package thinking
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LLMEngine implements Engine using an OpenAI-compatible API.
|
||||
// Works with OpenAI, claw402 (x402), DeepSeek, Qwen, etc.
|
||||
type LLMEngine struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
model string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewLLMEngine creates a new LLM-backed thinking engine.
|
||||
func NewLLMEngine(baseURL, apiKey, model string) *LLMEngine {
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.openai.com/v1"
|
||||
}
|
||||
return &LLMEngine{
|
||||
baseURL: baseURL,
|
||||
apiKey: apiKey,
|
||||
model: model,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 120 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// chatRequest is the OpenAI chat completions request body.
|
||||
type chatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
}
|
||||
|
||||
// chatResponse is the OpenAI chat completions response body.
|
||||
type chatResponse struct {
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Chat sends messages to the LLM and returns the response.
|
||||
func (e *LLMEngine) Chat(ctx context.Context, messages []Message) (string, error) {
|
||||
reqBody := chatRequest{
|
||||
Model: e.model,
|
||||
Messages: messages,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", e.baseURL+"/chat/completions", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if e.apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+e.apiKey)
|
||||
}
|
||||
|
||||
resp, err := e.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("http request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("LLM API error (status %d): %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var chatResp chatResponse
|
||||
if err := json.Unmarshal(respBody, &chatResp); err != nil {
|
||||
return "", fmt.Errorf("unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if chatResp.Error != nil {
|
||||
return "", fmt.Errorf("LLM error: %s", chatResp.Error.Message)
|
||||
}
|
||||
|
||||
if len(chatResp.Choices) == 0 {
|
||||
return "", fmt.Errorf("LLM returned no choices")
|
||||
}
|
||||
|
||||
return chatResp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
// Analyze sends an analysis prompt and parses the AI response.
|
||||
func (e *LLMEngine) Analyze(ctx context.Context, prompt string) (*Analysis, error) {
|
||||
systemPrompt := `You are NOFXi, an expert AI trading analyst. Analyze the given market data and provide a trading recommendation.
|
||||
|
||||
Respond in JSON format:
|
||||
{
|
||||
"action": "buy|sell|hold|wait",
|
||||
"symbol": "BTC/USDT",
|
||||
"confidence": 0.85,
|
||||
"reasoning": "Brief explanation",
|
||||
"stop_loss": 0.0,
|
||||
"take_profit": 0.0
|
||||
}
|
||||
|
||||
Be concise. Only recommend high-confidence trades.`
|
||||
|
||||
messages := []Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: prompt},
|
||||
}
|
||||
|
||||
resp, err := e.Chat(ctx, messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var analysis Analysis
|
||||
if err := json.Unmarshal([]byte(resp), &analysis); err != nil {
|
||||
// If JSON parsing fails, return the raw text as reasoning
|
||||
return &Analysis{
|
||||
Action: "hold",
|
||||
Reasoning: resp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &analysis, nil
|
||||
}
|
||||
9
nofxi/internal/thinking/thinking.go
Normal file
9
nofxi/internal/thinking/thinking.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Package thinking implements the Thinking Layer.
|
||||
//
|
||||
// Provides AI-powered decision making via OpenAI-compatible APIs.
|
||||
// Supports multiple providers: OpenAI, claw402 (x402), DeepSeek, Qwen, etc.
|
||||
//
|
||||
// Key interfaces:
|
||||
// - Engine: core AI decision interface (Chat + Analyze)
|
||||
// - LLMEngine: concrete implementation using HTTP/REST
|
||||
package thinking
|
||||
Reference in New Issue
Block a user