feat(market): Add 15m/1h timeframes for comprehensive trend analysis

## Problem
Merged WebSocket architecture (nofxaios/dev) only supported 3m/4h intervals,
but adaptive.txt v5.5.6.1 requires 15m/1h data for:
- BTC state evaluation (line 105-107)
- Multi-confirmation checklist (line 146, 159)
- False breakout detection (line 176, 182)
- Confidence scoring (line 197)

## Solution
Add 15m and 1h timeframe support across WebSocket pipeline:

### 1. market/monitor.go (+64/-40 lines)
- Add klineDataMap15m and klineDataMap1h to WSMonitor struct
- Update subKlineTime: ["3m", "4h"] → ["3m", "15m", "1h", "4h"]
- Extend initializeHistoricalData() to load 15m/1h historical klines
- Update getKlineDataMap() switch to handle all 4 timeframes
- Fix bug: line 109 used wrong variable (klines vs klines4h)

### 2. market/types.go (+26/-1 lines)
- Add MidTermData15m struct (15-minute short-term trend filtering)
- Add MidTermData1h struct (1-hour mid-term trend confirmation)
- Update Data struct to include:
  - MidTermSeries15m *MidTermData15m
  - MidTermSeries1h *MidTermData1h

### 3. market/data.go (+171/-2 lines)
- Update Get() to fetch klines15m and klines1h via WebSocket
- Implement calculateMidTermSeries15m() - computes EMA20, MACD, RSI7/14 for 15m
- Implement calculateMidTermSeries1h() - computes EMA20, MACD, RSI7/14 for 1h
- Update Format() to output 15m/1h series data for AI prompt context

## Impact Assessment
### WebSocket Load (Binance limit: 1024 streams/connection)
- 8 coins × 4 timeframes = 32 streams (3% usage) 
- 100 coins × 4 timeframes = 400 streams (39% usage) 
- 250 coins × 4 timeframes = 1000 streams (98% usage) ⚠️

### Benefits
- Enables adaptive.txt standard mode: 15m/1h/4h multi-timeframe confirmation
- Restores false breakout detection: 15m RSI vs 1h RSI divergence checks
- Improves confidence scoring: 15m/1h/4h MACD alignment validation
- Zero REST API calls (WebSocket cache, no rate limit risk)

## Testing Notes
- Monitor initial subscription logs for "已加载 X 的历史K线数据-15m/1h"
- Verify AI prompts contain "Mid-term series (15-minute intervals)" section
- Check decision logs reference 15m/1h indicators in reasoning

Related: adaptive.txt v5.5.6.1 requirements, NoFxAiOS/dev WebSocket merge
This commit is contained in:
ZhouYongyou
2025-11-03 19:17:12 +08:00
parent bead75ef8f
commit 8d12514498
3 changed files with 231 additions and 30 deletions

View File

@@ -15,9 +15,11 @@ type WSMonitor struct {
symbols []string
featuresMap sync.Map
alertsChan chan Alert
klineDataMap3m sync.Map // 存储每个交易对的K线历史数据
klineDataMap4h sync.Map // 存储每个交易对的K线历史数据
tickerDataMap sync.Map // 存储每个交易对的ticker数据
klineDataMap3m sync.Map // 存储每个交易对的K线历史数据
klineDataMap15m sync.Map // 存储每个交易对的15分钟K线历史数据
klineDataMap1h sync.Map // 存储每个交易对的1小时K线历史数据
klineDataMap4h sync.Map // 存储每个交易对的K线历史数据
tickerDataMap sync.Map // 存储每个交易对的ticker数据
batchSize int
filterSymbols sync.Map // 使用sync.Map来存储需要监控的币种和其状态
symbolStats sync.Map // 存储币种统计信息
@@ -32,7 +34,7 @@ type SymbolStats struct {
}
var WSMonitorCli *WSMonitor
var subKlineTime = []string{"3m", "4h"} // 管理订阅流的K线周期
var subKlineTime = []string{"3m", "15m", "1h", "4h"} // 管理订阅流的K线周期
func NewWSMonitor(batchSize int) *WSMonitor {
WSMonitorCli = &WSMonitor{
@@ -89,25 +91,40 @@ func (m *WSMonitor) initializeHistoricalData() error {
defer wg.Done()
defer func() { <-semaphore }()
// 获取历史K线数据
klines, err := apiClient.GetKlines(s, "3m", 100)
// 获取3分钟历史K线数据
klines3m, err := apiClient.GetKlines(s, "3m", 100)
if err != nil {
log.Printf("获取 %s 历史数据失败: %v", s, err)
return
log.Printf("获取 %s 3m历史数据失败: %v", s, err)
} else if len(klines3m) > 0 {
m.klineDataMap3m.Store(s, klines3m)
log.Printf("已加载 %s 的历史K线数据-3m: %d 条", s, len(klines3m))
}
if len(klines) > 0 {
m.klineDataMap3m.Store(s, klines)
log.Printf("已加载 %s 的历史K线数据-3m: %d 条", s, len(klines))
// 获取15分钟历史K线数据
klines15m, err := apiClient.GetKlines(s, "15m", 100)
if err != nil {
log.Printf("获取 %s 15m历史数据失败: %v", s, err)
} else if len(klines15m) > 0 {
m.klineDataMap15m.Store(s, klines15m)
log.Printf("已加载 %s 的历史K线数据-15m: %d 条", s, len(klines15m))
}
// 获取历史K线数据
// 获取1小时历史K线数据
klines1h, err := apiClient.GetKlines(s, "1h", 100)
if err != nil {
log.Printf("获取 %s 1h历史数据失败: %v", s, err)
} else if len(klines1h) > 0 {
m.klineDataMap1h.Store(s, klines1h)
log.Printf("已加载 %s 的历史K线数据-1h: %d 条", s, len(klines1h))
}
// 获取4小时历史K线数据
klines4h, err := apiClient.GetKlines(s, "4h", 100)
if err != nil {
log.Printf("获取 %s 历史数据失败: %v", s, err)
return
}
if len(klines4h) > 0 {
m.klineDataMap4h.Store(s, klines)
log.Printf("已加载 %s 的历史K线数据-4h: %d 条", s, len(klines))
log.Printf("获取 %s 4h历史数据失败: %v", s, err)
} else if len(klines4h) > 0 {
m.klineDataMap4h.Store(s, klines4h)
log.Printf("已加载 %s 的历史K线数据-4h: %d 条", s, len(klines4h))
}
}(symbol)
}
@@ -180,11 +197,16 @@ func (m *WSMonitor) handleKlineData(symbol string, ch <-chan []byte, _time strin
func (m *WSMonitor) getKlineDataMap(_time string) *sync.Map {
var klineDataMap *sync.Map
if _time == "3m" {
switch _time {
case "3m":
klineDataMap = &m.klineDataMap3m
} else if _time == "4h" {
case "15m":
klineDataMap = &m.klineDataMap15m
case "1h":
klineDataMap = &m.klineDataMap1h
case "4h":
klineDataMap = &m.klineDataMap4h
} else {
default:
klineDataMap = &sync.Map{}
}
return klineDataMap