Files
nofx/docs/architecture/STRATEGY_MODULE.md
T
2025-12-15 18:07:52 +08:00

738 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NOFX Strategy Module - Technical Documentation
**Language:** [English](STRATEGY_MODULE.md) | [中文](STRATEGY_MODULE.zh-CN.md)
## Overview
This document describes the complete data flow of the NOFX strategy module, including coin selection, data assembly, prompt construction, AI request, response parsing, and decision execution.
---
## Complete Data Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Trading Cycle (Every N Minutes) │
└─────────────────────────────────────────────────────────────────┘
1. Coin Selection (GetCandidateCoins)
├─ Static (Static list)
├─ AI500 Pool (AI rating pool)
├─ OI Top (Position growth ranking)
└─ Mixed (Mixed mode)
2. Data Assembly (buildTradingContext)
├─ Account balance → equity, available, unrealizedPnL
├─ Current positions → symbol, side, entry, mark, qty, leverage
├─ K-line data → OHLCV (5m, 15m, 1h, 4h)
├─ Technical indicators → EMA, MACD, RSI, ATR, Volume
├─ On-chain data → OI, Funding Rate
├─ Quant data → Capital flow, OI changes (optional)
└─ Recent trades → Last 10 closed trades
3. System Prompt (BuildSystemPrompt)
├─ Role definition
├─ Trading mode (aggressive/conservative/scalping)
├─ Hard constraints (code enforced)
├─ AI guidance (suggested values)
├─ Trading frequency
├─ Entry standards
├─ Decision process
└─ Output format (XML + JSON)
4. User Prompt (BuildUserPrompt)
├─ System status (time, cycle number)
├─ BTC market overview
├─ Account information
├─ Current positions (with indicators)
├─ Candidate coins (full market data)
└─ "Please analyze and output decisions..."
5. AI Request (CallWithMessages)
├─ Select AI model
├─ POST: system_prompt + user_prompt
├─ Timeout: 120s, Retries: 3
└─ Return raw response
6. AI Parsing (parseFullDecisionResponse)
├─ Extract Chain of Thought <reasoning>
├─ Extract JSON decision <decision>
├─ Fix character encoding
├─ Validate JSON format
├─ Parse decision array
└─ Validate risk parameters
7. Decision Execution
├─ Sort: Close first → Open → hold/wait
├─ Risk control enforcement
├─ Submit orders
├─ Confirm fills
└─ Record to database
```
---
## 1. Coin Selection
**Core File:** `decision/engine.go:380-454`
**Entry Method:** `StrategyEngine.GetCandidateCoins()`
### 1.1 Static Coin List
```go
// decision/engine.go:395-403
if config.CoinSource.SourceType == "static" {
for _, symbol := range config.CoinSource.StaticCoins {
coins = append(coins, CandidateCoin{
Symbol: market.Normalize(symbol),
Sources: []string{"static"},
})
}
}
```
- **Config:** `StrategyConfig.CoinSource.StaticCoins`
- **Usage:** Manually specify trading coins
- **Tag:** `["static"]`
### 1.2 AI500 Coin Pool
```go
// decision/engine.go:405-406, 456-474
func (e *StrategyEngine) getCoinPoolCoins(limit int) []CandidateCoin {
coins, err := e.provider.GetTopRatedCoins(limit)
// ...
for _, coin := range coins {
result = append(result, CandidateCoin{
Symbol: coin.Symbol,
Sources: []string{"ai500"},
})
}
}
```
- **API:** `config.CoinSource.CoinPoolAPIURL`
- **Usage:** Get top N coins by AI rating
- **Tag:** `["ai500"]`
### 1.3 OI Top Coins (Position Growth Ranking)
```go
// decision/engine.go:408-409, 476-498
func (e *StrategyEngine) getOITopCoins() []CandidateCoin {
positions, err := e.provider.GetOITopPositions()
// ...
for _, pos := range positions {
result = append(result, CandidateCoin{
Symbol: pos.Symbol,
Sources: []string{"oi_top"},
})
}
}
```
- **API:** `config.CoinSource.OITopAPIURL`
- **Usage:** Get coins with fastest OI growth
- **Tag:** `["oi_top"]`
### 1.4 Mixed Mode
```go
// decision/engine.go:411-449
if config.CoinSource.SourceType == "mixed" {
if config.CoinSource.UseCoinPool {
// Add AI500 coins
}
if config.CoinSource.UseOITop {
// Add OI Top coins
}
if len(config.CoinSource.StaticCoins) > 0 {
// Add static coins
}
// Deduplicate and merge, keep multi-source tags
}
```
- **Feature:** Use multiple data sources simultaneously
- **Tag Example:** `["ai500", "oi_top"]` (dual signal coin)
---
## 2. Data Assembly
**Core File:** `trader/auto_trader.go:562-791`, `decision/engine.go:299-374`
**Entry Method:** `AutoTrader.buildTradingContext()`
### 2.1 Account Data
```go
// trader/auto_trader.go:565-583
balance, err := at.trader.GetBalance()
equity := balance["total_equity"].(float64)
available := balance["available_balance"].(float64)
unrealizedPnL := balance["total_pnl"].(float64)
```
**Extracted Fields:**
- `total_equity` - Total account equity
- `available_balance` - Available balance
- `total_pnl` - Unrealized PnL
### 2.2 Position Data
```go
// trader/auto_trader.go:588-682
positions, err := at.trader.GetPositions()
for _, pos := range positions {
position := decision.Position{
Symbol: pos.Symbol,
Side: pos.Side, // "long" / "short"
EntryPrice: pos.EntryPrice,
MarkPrice: pos.MarkPrice,
Quantity: pos.Quantity,
Leverage: pos.Leverage,
UnrealizedPnL: pos.UnrealizedPnL,
LiquidationPrice: pos.LiquidationPrice,
}
}
```
### 2.3 Market Data Fetching
```go
// decision/engine.go:299-374
func (e *StrategyEngine) fetchMarketDataWithStrategy(symbols []string) map[string]*market.Data {
timeframes := config.Indicators.Klines.SelectedTimeframes // ["5m", "15m", "1h", "4h"]
primaryTF := config.Indicators.Klines.PrimaryTimeframe // "5m"
count := config.Indicators.Klines.PrimaryCount // 30
for _, symbol := range symbols {
data := market.GetWithTimeframes(symbol, timeframes, primaryTF, count)
result[symbol] = data
}
}
```
### 2.4 Technical Indicator Calculation
**File:** `market/data.go:59-98`
| Indicator | Config | Calculation |
|-----------|--------|-------------|
| **EMA** | `EnableEMA`, `EMAPeriods` | `calculateEMA(klines, period)` |
| **MACD** | `EnableMACD` | `calculateMACD(klines)` - 12/26/9 |
| **RSI** | `EnableRSI`, `RSIPeriods` | `calculateRSI(klines, period)` |
| **ATR** | `EnableATR`, `ATRPeriods` | `calculateATR(klines, period)` |
| **Volume** | `EnableVolume` | Raw volume data |
| **OI** | `EnableOI` | Open interest data |
| **Funding Rate** | `EnableFundingRate` | Funding rate |
### 2.5 Quant Data (Optional)
```go
// trader/auto_trader.go:759-778
if config.Indicators.EnableQuantData {
quantData := provider.GetQuantData(symbol)
// Contains: Capital flow, OI changes, Price changes
}
```
**Data Structure:**
```go
QuantData {
Netflow {
Institution: {Future, Spot}, // Institutional flow
Personal: {Future, Spot} // Retail flow
},
OI {
CurrentOI: float64,
Delta: {1h, 4h, 24h} // OI changes
},
PriceChange {
"1h", "4h", "24h": float64 // Price change %
}
}
```
---
## 3. System Prompt
**Core File:** `decision/engine.go:700-818`
**Entry Method:** `StrategyEngine.BuildSystemPrompt(accountEquity, variant)`
### 3.1 Prompt Structure (8 Sections)
```
1. Role Definition [Editable]
2. Trading Mode Variant [Runtime determined]
3. Hard Constraints [Code enforced + AI guided]
4. Trading Frequency [Editable]
5. Entry Standards [Editable]
6. Decision Process [Editable]
7. Output Format [Fixed XML + JSON structure]
8. Custom Prompt [Optional]
```
### 3.2 Role Definition
```go
// decision/engine.go:706-713
roleDefinition := config.PromptSections.RoleDefinition
if roleDefinition == "" {
roleDefinition = "You are a professional cryptocurrency trading AI..."
}
```
### 3.3 Trading Mode Variants
| Mode | Characteristics |
|------|-----------------|
| `aggressive` | Trend breakout, higher position tolerance |
| `conservative` | Multi-signal confirmation, conservative money management |
| `scalping` | Short-term momentum, tight take-profit |
### 3.4 Hard Constraints
**Code Enforced:**
```go
// decision/engine.go:725-749
maxPositions := config.RiskControl.MaxPositions // Default: 3
altcoinMaxRatio := config.RiskControl.AltcoinMaxPositionValueRatio // Default: 1.0
btcethMaxRatio := config.RiskControl.BTCETHMaxPositionValueRatio // Default: 5.0
maxMarginUsage := config.RiskControl.MaxMarginUsage // Default: 90%
minPositionSize := config.RiskControl.MinPositionSize // Default: 12 USDT
```
**AI Guided (Suggested Values):**
```go
altcoinMaxLeverage := config.RiskControl.AltcoinMaxLeverage // Default: 5x
btcethMaxLeverage := config.RiskControl.BTCETHMaxLeverage // Default: 5x
minRiskRewardRatio := config.RiskControl.MinRiskRewardRatio // Default: 1:3
minConfidence := config.RiskControl.MinConfidence // Default: 75
```
### 3.5 Output Format Requirements
```xml
<reasoning>
[Chain of Thought analysis process]
</reasoning>
<decision>
```json
[
{
"symbol": "BTCUSDT",
"action": "open_long",
"leverage": 5,
"position_size_usd": 100.00,
"stop_loss": 65000.00,
"take_profit": 72000.00,
"confidence": 85,
"risk_usd": 20.00,
"reasoning": "..."
}
]
```
</decision>
```
---
## 4. User Prompt
**Core File:** `decision/engine.go:884-1007`
**Entry Method:** `StrategyEngine.BuildUserPrompt(ctx)`
### 4.1 Prompt Content Structure
```
1. System Status [Time, cycle number, runtime]
2. BTC Market Overview [Price, change%, MACD, RSI]
3. Account Info [Equity, balance%, PnL%, margin%, positions]
4. Recent Trades [Last 10 closed trades]
5. Current Positions [Detailed position data + indicators]
6. Candidate Coins [Full market data]
7. Quant Data [Capital flow, OI data] (optional)
8. OI Ranking Data [Market OI change ranking] (optional)
```
### 4.2 Account Info Format
```
Account: Equity 1000.00 | Balance 800.00 (80.0%) | PnL +5.5% | Margin 20.0% | Positions 2
```
### 4.3 Position Info Format
```
1. BTCUSDT LONG | Entry 68000.0000 Current 69500.0000
Qty 0.0100 | Position Value $695.00
PnL +2.21% | Amount +$15.00
Peak PnL +3.50% | Leverage 5x
Margin $139.00 | Liquidation Price 55000.0000
Holding Duration 2 hours 30 minutes
Market: price=69500, ema20=68800, macd=150.5, rsi7=62.3
OI: Latest=15000000, Avg=14500000
Funding Rate: 0.0100%
```
### 4.4 Candidate Coin Format
```
### 1. ETHUSDT (AI500+OI_Top dual signal)
current_price = 3500.00, current_ema20 = 3450.00, current_macd = 25.5, current_rsi7 = 58.0
Open Interest: Latest: 8500000.00 Average: 8200000.00
Funding Rate: 0.0050
=== 5M TIMEFRAME (oldest → latest) ===
Prices: [3480, 3485, 3490, 3495, 3500]
Volumes: [1000, 1200, 1100, 1300, 1150]
EMA20: [3470, 3475, 3478, 3482, 3485]
MACD: [20.1, 21.5, 22.8, 24.0, 25.5]
RSI7: [55.0, 56.2, 57.1, 57.8, 58.0]
=== 15M TIMEFRAME ===
...
```
---
## 5. AI Request
**Core File:** `decision/engine.go:222-293`, `mcp/client.go:136-150`
### 5.1 Request Flow
```go
// decision/engine.go:263-268
aiCallStart := time.Now()
aiResponse, err := mcpClient.CallWithMessages(systemPrompt, userPrompt)
aiCallDuration := time.Since(aiCallStart)
```
### 5.2 Supported AI Models
| Model | Client File | Default Model |
|-------|-------------|---------------|
| **DeepSeek** | `mcp/deepseek_client.go` | deepseek-chat |
| **Qwen** | `mcp/qwen_client.go` | qwen-max |
| **Claude** | `mcp/claude_client.go` | claude-3-5-sonnet |
| **Gemini** | `mcp/gemini_client.go` | gemini-pro |
| **Grok** | `mcp/grok_client.go` | grok-beta |
| **OpenAI** | `mcp/openai_client.go` | gpt-5.2 |
| **Kimi** | `mcp/kimi_client.go` | moonshot-v1-8k |
### 5.3 Request Parameters
```go
// mcp/client.go
Timeout: 120 seconds
MaxRetries: 3
RetryDelay: 2 seconds (exponential backoff)
```
---
## 6. AI Response Parsing
**Core File:** `decision/engine.go:1303-1604`
**Entry Method:** `parseFullDecisionResponse(response, accountEquity, leverage, ratio)`
### 6.1 Parsing Flow
```
Raw AI Response (text)
1. Extract Chain of Thought [extractCoTTrace()]
2. Extract JSON Decision [extractDecisions()]
3. Validate JSON Format [validateJSONFormat()]
4. Parse JSON [json.Unmarshal()]
5. Validate Decisions [validateDecisions()]
6. Build FullDecision [Return structured result]
```
### 6.2 Chain of Thought Extraction
```go
// decision/engine.go:1327-1345
func extractCoTTrace(response string) string {
// Priority 1: <reasoning> XML tag
if match := reReasoningTag.FindStringSubmatch(response); len(match) > 1 {
return strings.TrimSpace(match[1])
}
// Priority 2: Text before <decision> tag
// Priority 3: Text before JSON [
// Priority 4: Full response
}
```
### 6.3 JSON Decision Extraction
```go
// decision/engine.go:1347-1408
func extractDecisions(response string) (string, error) {
// 1. Remove invisible characters
response = removeInvisibleRunes(response)
// 2. Fix character encoding
response = fixMissingQuotes(response)
// 3. Extract JSON (priority)
// - <decision> XML tag + ```json
// - Standalone ```json code block
// - Bare JSON array
}
```
### 6.4 Character Encoding Fix
```go
// decision/engine.go:1410-1432
func fixMissingQuotes(s string) string {
// Chinese quotes → ASCII
s = strings.ReplaceAll(s, """, "\"")
s = strings.ReplaceAll(s, """, "\"")
// Chinese brackets → ASCII
s = strings.ReplaceAll(s, "", "[")
s = strings.ReplaceAll(s, "", "]")
s = strings.ReplaceAll(s, "", "{")
s = strings.ReplaceAll(s, "", "}")
// Chinese punctuation → ASCII
s = strings.ReplaceAll(s, "", ":")
s = strings.ReplaceAll(s, "", ",")
}
```
### 6.5 Decision Validation
```go
// decision/engine.go:1480-1602
func validateDecisions(decisions []Decision, equity, leverage, ratio float64) error {
for _, d := range decisions {
// 1. Validate action type
validActions := []string{"open_long", "open_short", "close_long", "close_short", "hold", "wait"}
// 2. Open position validation
if isOpenAction(d.Action) {
// Leverage range check
// Position size check
// Stop loss/take profit check
// Risk/reward ratio check
// Confidence check
}
// 3. Close position validation
if isCloseAction(d.Action) {
// Symbol must exist
}
}
}
```
### 6.6 Decision Structure
```go
// decision/engine.go:128-143
type Decision struct {
Symbol string // Trading pair: "BTCUSDT"
Action string // "open_long", "open_short", "close_long", "close_short", "hold", "wait"
Leverage int // Leverage multiplier
PositionSizeUSD float64 // Position value (USDT)
StopLoss float64 // Stop loss price
TakeProfit float64 // Take profit price
Confidence int // Confidence 0-100
RiskUSD float64 // Max risk (USDT)
Reasoning string // Decision reasoning
}
```
---
## 7. Decision Execution
**Core File:** `trader/auto_trader.go:392-560`
### 7.1 Decision Sorting
```go
// trader/auto_trader.go:519-526
sort.SliceStable(decisions, func(i, j int) bool {
priority := map[string]int{
"close_long": 1, "close_short": 1, // Highest priority
"open_long": 2, "open_short": 2, // Second priority
"hold": 3, "wait": 3, // Lowest priority
}
return priority[decisions[i].Action] < priority[decisions[j].Action]
})
```
### 7.2 Risk Control Enforcement
**File:** `trader/auto_trader.go:1769-1851`
| Check | Method | Action |
|-------|--------|--------|
| Max positions | `enforceMaxPositions()` | Reject new opens |
| Position value cap | `enforcePositionValueRatio()` | Auto reduce size |
| Min position | `enforceMinPositionSize()` | Reject small orders |
| Margin adjustment | Auto calculate | Adjust by available balance |
### 7.3 Order Execution
```go
// trader/auto_trader.go:1631-1767
func (at *AutoTrader) recordAndConfirmOrder(orderID, symbol, side, action string) {
// 1. Poll order status (5 retries, 500ms interval)
for i := 0; i < 5; i++ {
status := at.trader.GetOrderStatus(orderID)
if status.Status == "FILLED" {
break
}
time.Sleep(500 * time.Millisecond)
}
// 2. Extract fill info
filledPrice := status.AvgPrice
filledQty := status.FilledQty
fee := status.Fee
// 3. Record to database
at.store.Position().SaveOrder(...)
}
```
### 7.4 Decision Log Saving
```go
// trader/auto_trader.go:1235-1256
record := &store.DecisionRecord{
CycleNumber: cycleNumber,
TraderID: traderID,
Timestamp: time.Now(),
SystemPrompt: systemPrompt, // Full system prompt
InputPrompt: userPrompt, // Full user prompt
CoTTrace: cotTrace, // AI chain of thought
DecisionJSON: decisionsJSON, // Parsed decisions
RawResponse: rawResponse, // Raw AI response
ExecutionLog: executionResults, // Execution results
CandidateCoins: candidateCoins, // Candidate coins
Success: success, // Execution status
}
at.store.Decision().LogDecision(record)
```
---
## Core File Index
| Module | File | Key Methods |
|--------|------|-------------|
| **Main Loop** | `trader/auto_trader.go` | `Run()`, `runCycle()`, `buildTradingContext()` |
| **Coin Selection** | `decision/engine.go:380-454` | `GetCandidateCoins()` |
| **Data Fetching** | `market/data.go` | `Get()`, `GetWithTimeframes()` |
| **Indicator Calc** | `market/data.go:59-98` | `calculateEMA()`, `calculateMACD()`, `calculateRSI()` |
| **System Prompt** | `decision/engine.go:700-818` | `BuildSystemPrompt()` |
| **User Prompt** | `decision/engine.go:884-1007` | `BuildUserPrompt()` |
| **Market Format** | `decision/engine.go:1029-1099` | `formatMarketData()` |
| **AI Request** | `decision/engine.go:222-293` | `GetFullDecisionWithStrategy()` |
| **MCP Client** | `mcp/client.go:136-150` | `CallWithMessages()` |
| **Response Parse** | `decision/engine.go:1303-1604` | `parseFullDecisionResponse()` |
| **CoT Extract** | `decision/engine.go:1327-1345` | `extractCoTTrace()` |
| **JSON Extract** | `decision/engine.go:1347-1408` | `extractDecisions()` |
| **Decision Valid** | `decision/engine.go:1480-1602` | `validateDecisions()` |
| **Risk Enforce** | `trader/auto_trader.go:1769-1851` | `enforceMaxPositions()`, `enforcePositionValueRatio()` |
| **Strategy Config** | `store/strategy.go` | `StrategyConfig`, `RiskControlConfig` |
| **Data Provider** | `provider/data_provider.go` | `GetAI500Data()`, `GetOITopPositions()` |
---
## Configuration Reference
### Strategy Config Structure
```go
// store/strategy.go
type StrategyConfig struct {
// Coin Source
CoinSource struct {
SourceType string // "static", "coinpool", "oi_top", "mixed"
StaticCoins []string // Static coin list
UseCoinPool bool // Use AI500
UseOITop bool // Use OI ranking
CoinPoolLimit int // AI500 fetch limit
CoinPoolAPIURL string // AI500 API URL
OITopAPIURL string // OI ranking API URL
}
// Technical Indicators
Indicators struct {
EnableEMA bool
EMAPeriods []int // [20, 50]
EnableMACD bool
EnableRSI bool
RSIPeriods []int // [7, 14]
EnableATR bool
ATRPeriods []int // [14]
EnableVolume bool
EnableOI bool
EnableFundingRate bool
EnableQuantData bool
EnableOIRanking bool
Klines struct {
PrimaryTimeframe string // "5m"
SelectedTimeframes []string // ["5m", "15m", "1h", "4h"]
PrimaryCount int // 30
}
}
// Risk Control
RiskControl struct {
MaxPositions int // Max positions
BTCETHMaxLeverage int // BTC/ETH max leverage
AltcoinMaxLeverage int // Altcoin max leverage
BTCETHMaxPositionValueRatio float64 // BTC/ETH position ratio cap
AltcoinMaxPositionValueRatio float64 // Altcoin position ratio cap
MaxMarginUsage float64 // Max margin usage
MinPositionSize float64 // Min position size
MinRiskRewardRatio float64 // Min risk/reward ratio
MinConfidence int // Min confidence
}
// Prompt Sections
PromptSections struct {
RoleDefinition string
TradingFrequency string
EntryStandards string
DecisionProcess string
}
// Custom Prompt
CustomPrompt string
}
```
---
**Document Version:** 1.0.0
**Last Updated:** 2025-01-15