diff --git a/market/data.go b/market/data.go index cd40be75..5b0f98d2 100644 --- a/market/data.go +++ b/market/data.go @@ -12,18 +12,31 @@ import ( // Get 获取指定代币的市场数据 func Get(symbol string) (*Data, error) { - var klines3m, klines4h []Kline + var klines3m, klines15m, klines1h, klines4h []Kline var err error // 标准化symbol symbol = Normalize(symbol) + // 获取3分钟K线数据 (最近10个) - klines3m, err = WSMonitorCli.GetCurrentKlines(symbol, "3m") // 多获取一些用于计算 + klines3m, err = WSMonitorCli.GetCurrentKlines(symbol, "3m") if err != nil { return nil, fmt.Errorf("获取3分钟K线失败: %v", err) } - // 获取4小时K线数据 (最近10个) - klines4h, err = WSMonitorCli.GetCurrentKlines(symbol, "4h") // 多获取用于计算指标 + // 获取15分钟K线数据 (最近40个) - 短期趋势 + klines15m, err = WSMonitorCli.GetCurrentKlines(symbol, "15m") + if err != nil { + return nil, fmt.Errorf("获取15分钟K线失败: %v", err) + } + + // 获取1小时K线数据 (最近60个) - 中期趋势 + klines1h, err = WSMonitorCli.GetCurrentKlines(symbol, "1h") + if err != nil { + return nil, fmt.Errorf("获取1小时K线失败: %v", err) + } + + // 获取4小时K线数据 (最近60个) - 长期趋势 + klines4h, err = WSMonitorCli.GetCurrentKlines(symbol, "4h") if err != nil { return nil, fmt.Errorf("获取4小时K线失败: %v", err) } @@ -63,10 +76,16 @@ func Get(symbol string) (*Data, error) { // 获取Funding Rate fundingRate, _ := getFundingRate(symbol) - // 计算日内系列数据 + // 计算日内系列数据 (3分钟) intradayData := calculateIntradaySeries(klines3m) - // 计算长期数据 + // 计算15分钟系列数据 + midTermData15m := calculateMidTermSeries15m(klines15m) + + // 计算1小时系列数据 + midTermData1h := calculateMidTermSeries1h(klines1h) + + // 计算长期数据 (4小时) longerTermData := calculateLongerTermData(klines4h) return &Data{ @@ -80,6 +99,8 @@ func Get(symbol string) (*Data, error) { OpenInterest: oiData, FundingRate: fundingRate, IntradaySeries: intradayData, + MidTermSeries15m: midTermData15m, + MidTermSeries1h: midTermData1h, LongerTermContext: longerTermData, }, nil } @@ -243,6 +264,96 @@ func calculateIntradaySeries(klines []Kline) *IntradayData { return data } +// calculateMidTermSeries15m 计算15分钟系列数据 +func calculateMidTermSeries15m(klines []Kline) *MidTermData15m { + data := &MidTermData15m{ + MidPrices: make([]float64, 0, 10), + EMA20Values: make([]float64, 0, 10), + MACDValues: make([]float64, 0, 10), + RSI7Values: make([]float64, 0, 10), + RSI14Values: make([]float64, 0, 10), + } + + // 获取最近10个数据点 + start := len(klines) - 10 + if start < 0 { + start = 0 + } + + for i := start; i < len(klines); i++ { + data.MidPrices = append(data.MidPrices, klines[i].Close) + + // 计算每个点的EMA20 + if i >= 19 { + ema20 := calculateEMA(klines[:i+1], 20) + data.EMA20Values = append(data.EMA20Values, ema20) + } + + // 计算每个点的MACD + if i >= 25 { + macd := calculateMACD(klines[:i+1]) + data.MACDValues = append(data.MACDValues, macd) + } + + // 计算每个点的RSI + if i >= 7 { + rsi7 := calculateRSI(klines[:i+1], 7) + data.RSI7Values = append(data.RSI7Values, rsi7) + } + if i >= 14 { + rsi14 := calculateRSI(klines[:i+1], 14) + data.RSI14Values = append(data.RSI14Values, rsi14) + } + } + + return data +} + +// calculateMidTermSeries1h 计算1小时系列数据 +func calculateMidTermSeries1h(klines []Kline) *MidTermData1h { + data := &MidTermData1h{ + MidPrices: make([]float64, 0, 10), + EMA20Values: make([]float64, 0, 10), + MACDValues: make([]float64, 0, 10), + RSI7Values: make([]float64, 0, 10), + RSI14Values: make([]float64, 0, 10), + } + + // 获取最近10个数据点 + start := len(klines) - 10 + if start < 0 { + start = 0 + } + + for i := start; i < len(klines); i++ { + data.MidPrices = append(data.MidPrices, klines[i].Close) + + // 计算每个点的EMA20 + if i >= 19 { + ema20 := calculateEMA(klines[:i+1], 20) + data.EMA20Values = append(data.EMA20Values, ema20) + } + + // 计算每个点的MACD + if i >= 25 { + macd := calculateMACD(klines[:i+1]) + data.MACDValues = append(data.MACDValues, macd) + } + + // 计算每个点的RSI + if i >= 7 { + rsi7 := calculateRSI(klines[:i+1], 7) + data.RSI7Values = append(data.RSI7Values, rsi7) + } + if i >= 14 { + rsi14 := calculateRSI(klines[:i+1], 14) + data.RSI14Values = append(data.RSI14Values, rsi14) + } + } + + return data +} + // calculateLongerTermData 计算长期数据 func calculateLongerTermData(klines []Kline) *LongerTermData { data := &LongerTermData{ @@ -396,6 +507,54 @@ func Format(data *Data) string { } } + if data.MidTermSeries15m != nil { + sb.WriteString("Mid‑term series (15‑minute intervals, oldest → latest):\n\n") + + if len(data.MidTermSeries15m.MidPrices) > 0 { + sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidTermSeries15m.MidPrices))) + } + + if len(data.MidTermSeries15m.EMA20Values) > 0 { + sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.EMA20Values))) + } + + if len(data.MidTermSeries15m.MACDValues) > 0 { + sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MidTermSeries15m.MACDValues))) + } + + if len(data.MidTermSeries15m.RSI7Values) > 0 { + sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.RSI7Values))) + } + + if len(data.MidTermSeries15m.RSI14Values) > 0 { + sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.RSI14Values))) + } + } + + if data.MidTermSeries1h != nil { + sb.WriteString("Mid‑term series (1‑hour intervals, oldest → latest):\n\n") + + if len(data.MidTermSeries1h.MidPrices) > 0 { + sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidTermSeries1h.MidPrices))) + } + + if len(data.MidTermSeries1h.EMA20Values) > 0 { + sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.EMA20Values))) + } + + if len(data.MidTermSeries1h.MACDValues) > 0 { + sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MidTermSeries1h.MACDValues))) + } + + if len(data.MidTermSeries1h.RSI7Values) > 0 { + sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.RSI7Values))) + } + + if len(data.MidTermSeries1h.RSI14Values) > 0 { + sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.RSI14Values))) + } + } + if data.LongerTermContext != nil { sb.WriteString("Longer‑term context (4‑hour timeframe):\n\n") diff --git a/market/monitor.go b/market/monitor.go index 337640d8..2325dd9c 100644 --- a/market/monitor.go +++ b/market/monitor.go @@ -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 diff --git a/market/types.go b/market/types.go index 82f44415..60553e49 100644 --- a/market/types.go +++ b/market/types.go @@ -13,8 +13,10 @@ type Data struct { CurrentRSI7 float64 OpenInterest *OIData FundingRate float64 - IntradaySeries *IntradayData - LongerTermContext *LongerTermData + IntradaySeries *IntradayData // 3分钟数据 - 实时价格 + MidTermSeries15m *MidTermData15m // 15分钟数据 - 短期趋势 + MidTermSeries1h *MidTermData1h // 1小时数据 - 中期趋势 + LongerTermContext *LongerTermData // 4小时数据 - 长期趋势 } // OIData Open Interest数据 @@ -23,7 +25,7 @@ type OIData struct { Average float64 } -// IntradayData 日内数据(3分钟间隔) +// IntradayData 日内数据(3分钟间隔) - 主要用于获取实时价格 type IntradayData struct { MidPrices []float64 EMA20Values []float64 @@ -32,6 +34,24 @@ type IntradayData struct { RSI14Values []float64 } +// MidTermData15m 15分钟时间框架数据 - 短期趋势过滤 +type MidTermData15m struct { + MidPrices []float64 + EMA20Values []float64 + MACDValues []float64 + RSI7Values []float64 + RSI14Values []float64 +} + +// MidTermData1h 1小时时间框架数据 - 中期趋势确认 +type MidTermData1h struct { + MidPrices []float64 + EMA20Values []float64 + MACDValues []float64 + RSI7Values []float64 + RSI14Values []float64 +} + // LongerTermData 长期数据(4小时时间框架) type LongerTermData struct { EMA20 float64