diff --git a/decision/engine.go b/decision/engine.go index b46acc03..a2b67077 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -868,6 +868,14 @@ func (e *StrategyEngine) writeAvailableIndicators(sb *strings.Builder) { sb.WriteString("\n") } + if indicators.EnableBOLL { + sb.WriteString("- Bollinger Bands (BOLL) - Upper/Middle/Lower bands") + if len(indicators.BOLLPeriods) > 0 { + sb.WriteString(fmt.Sprintf(" (periods: %v)", indicators.BOLLPeriods)) + } + sb.WriteString("\n") + } + if indicators.EnableVolume { sb.WriteString("- Volume data\n") } @@ -1193,6 +1201,12 @@ func (e *StrategyEngine) formatTimeframeSeriesData(sb *strings.Builder, data *ma sb.WriteString(fmt.Sprintf("ATR14: %.4f\n", data.ATR14)) } + if indicators.EnableBOLL && len(data.BOLLUpper) > 0 { + sb.WriteString(fmt.Sprintf("BOLL Upper: %s\n", formatFloatSlice(data.BOLLUpper))) + sb.WriteString(fmt.Sprintf("BOLL Middle: %s\n", formatFloatSlice(data.BOLLMiddle))) + sb.WriteString(fmt.Sprintf("BOLL Lower: %s\n", formatFloatSlice(data.BOLLLower))) + } + sb.WriteString("\n") } diff --git a/market/data.go b/market/data.go index 01d12040..fbf536b4 100644 --- a/market/data.go +++ b/market/data.go @@ -227,6 +227,9 @@ func calculateTimeframeSeries(klines []Kline, timeframe string, count int) *Time RSI7Values: make([]float64, 0, count), RSI14Values: make([]float64, 0, count), Volume: make([]float64, 0, count), + BOLLUpper: make([]float64, 0, count), + BOLLMiddle: make([]float64, 0, count), + BOLLLower: make([]float64, 0, count), } // Get latest N data points based on count from config @@ -277,6 +280,14 @@ func calculateTimeframeSeries(klines []Kline, timeframe string, count int) *Time rsi14 := calculateRSI(klines[:i+1], 14) data.RSI14Values = append(data.RSI14Values, rsi14) } + + // Calculate Bollinger Bands (period 20, std dev multiplier 2) + if i >= 19 { + upper, middle, lower := calculateBOLL(klines[:i+1], 20, 2.0) + data.BOLLUpper = append(data.BOLLUpper, upper) + data.BOLLMiddle = append(data.BOLLMiddle, middle) + data.BOLLLower = append(data.BOLLLower, lower) + } } // Calculate ATR14 @@ -466,6 +477,36 @@ func calculateATR(klines []Kline, period int) float64 { return atr } +// calculateBOLL calculates Bollinger Bands (upper, middle, lower) +// period: typically 20, multiplier: typically 2 +func calculateBOLL(klines []Kline, period int, multiplier float64) (upper, middle, lower float64) { + if len(klines) < period { + return 0, 0, 0 + } + + // Calculate SMA (middle band) + sum := 0.0 + for i := len(klines) - period; i < len(klines); i++ { + sum += klines[i].Close + } + sma := sum / float64(period) + + // Calculate standard deviation + variance := 0.0 + for i := len(klines) - period; i < len(klines); i++ { + diff := klines[i].Close - sma + variance += diff * diff + } + stdDev := math.Sqrt(variance / float64(period)) + + // Calculate bands + middle = sma + upper = sma + multiplier*stdDev + lower = sma - multiplier*stdDev + + return upper, middle, lower +} + // calculateIntradaySeries calculates intraday series data func calculateIntradaySeries(klines []Kline) *IntradayData { data := &IntradayData{ diff --git a/market/types.go b/market/types.go index 7c1a5797..1ca71a68 100644 --- a/market/types.go +++ b/market/types.go @@ -41,6 +41,10 @@ type TimeframeSeriesData struct { RSI14Values []float64 `json:"rsi14_values"` // RSI14 series Volume []float64 `json:"volume"` // Volume series (deprecated, use Klines) ATR14 float64 `json:"atr14"` // ATR14 + // Bollinger Bands (period 20, std dev multiplier 2) + BOLLUpper []float64 `json:"boll_upper"` // Upper band + BOLLMiddle []float64 `json:"boll_middle"` // Middle band (SMA) + BOLLLower []float64 `json:"boll_lower"` // Lower band } // OIData Open Interest data diff --git a/store/strategy.go b/store/strategy.go index a016e2b2..e5f78bf9 100644 --- a/store/strategy.go +++ b/store/strategy.go @@ -82,6 +82,7 @@ type IndicatorConfig struct { EnableMACD bool `json:"enable_macd"` EnableRSI bool `json:"enable_rsi"` EnableATR bool `json:"enable_atr"` + EnableBOLL bool `json:"enable_boll"` // Bollinger Bands EnableVolume bool `json:"enable_volume"` EnableOI bool `json:"enable_oi"` // open interest EnableFundingRate bool `json:"enable_funding_rate"` // funding rate @@ -91,6 +92,8 @@ type IndicatorConfig struct { RSIPeriods []int `json:"rsi_periods,omitempty"` // default [7, 14] // ATR period configuration ATRPeriods []int `json:"atr_periods,omitempty"` // default [14] + // BOLL period configuration (period, standard deviation multiplier is fixed at 2) + BOLLPeriods []int `json:"boll_periods,omitempty"` // default [20] - can select multiple timeframes // external data sources ExternalDataSources []ExternalDataSource `json:"external_data_sources,omitempty"` // quantitative data sources (capital flow, position changes, price changes) @@ -241,12 +244,14 @@ func GetDefaultStrategyConfig(lang string) StrategyConfig { EnableMACD: false, EnableRSI: false, EnableATR: false, + EnableBOLL: false, EnableVolume: true, EnableOI: true, EnableFundingRate: true, EMAPeriods: []int{20, 50}, RSIPeriods: []int{7, 14}, ATRPeriods: []int{14}, + BOLLPeriods: []int{20}, EnableQuantData: true, QuantDataAPIURL: "http://nofxaios.com:30006/api/coin/{symbol}?include=netflow,oi,price&auth=cm_568c67eae410d912c54c", EnableQuantOI: true, diff --git a/web/src/components/strategy/IndicatorEditor.tsx b/web/src/components/strategy/IndicatorEditor.tsx index afa78ef4..69d4c04d 100644 --- a/web/src/components/strategy/IndicatorEditor.tsx +++ b/web/src/components/strategy/IndicatorEditor.tsx @@ -72,6 +72,8 @@ export function IndicatorEditor({ rsiDesc: { zh: '相对强弱指标', en: 'Relative Strength Index' }, atr: { zh: 'ATR', en: 'ATR' }, atrDesc: { zh: '真实波幅均值', en: 'Average True Range' }, + boll: { zh: 'BOLL 布林带', en: 'Bollinger Bands' }, + bollDesc: { zh: '布林带指标(上中下轨)', en: 'Upper/Middle/Lower Bands' }, volume: { zh: '成交量', en: 'Volume' }, volumeDesc: { zh: '交易量分析', en: 'Trading volume analysis' }, oi: { zh: '持仓量', en: 'Open Interest' }, @@ -295,6 +297,7 @@ export function IndicatorEditor({ { key: 'enable_macd', label: 'macd', desc: 'macdDesc', color: '#a855f7' }, { key: 'enable_rsi', label: 'rsi', desc: 'rsiDesc', color: '#F6465D', periodKey: 'rsi_periods', defaultPeriods: '7,14' }, { key: 'enable_atr', label: 'atr', desc: 'atrDesc', color: '#60a5fa', periodKey: 'atr_periods', defaultPeriods: '14' }, + { key: 'enable_boll', label: 'boll', desc: 'bollDesc', color: '#ec4899', periodKey: 'boll_periods', defaultPeriods: '20' }, ].map(({ key, label, desc, color, periodKey, defaultPeriods }) => (