package indicator /* 以MACD指标红绿柱(能量柱)为核心决策依据的A股交易规则系统。旨在通过捕捉柱体的“由绿变红”(空转多)和“由红变绿”(多转空)的临界点,结合柱体的伸缩长度及与股价的背离关系,来量化买卖信号,减少主观情绪干扰。 系统需能识别并提示以下几类基于红绿柱的买入场景: 参数: 日线 参数(12,26,9) 3.1 基础买入信号(柱体反转) 需求描述:捕捉下跌动能衰竭、上涨动能启动的瞬间。 判定标准: 在零轴下方或零轴附近。 MACD绿色柱体开始缩短的最后一根(或第一根红色柱体出现)。 量化确认:当MACD柱状线由绿变红(负值变正值),视为买入信号。根据上海证券报的历史回测,在周线级别出现红柱且数值超过5后的第二周开盘买入,长期收益显著。 3.2 趋势跟随买入(红柱伸长) 需求描述:在多头趋势中,捕捉主升浪阶段。 判定标准: DIF与DEA在零轴上方运行(水上)。 红柱连续增长(每一根比前一根高),表明上涨动能持续增强。 持有规则:只要红柱持续伸长,继续持有;若虽为红柱但开始缩短,则应考虑部分止盈。 3.4 底背离买入(左侧交易) 需求描述:捕捉价格新低但动能不再的潜在反转点。 判定标准: 股价创出新低(或收盘价新低)。 MACD绿柱的底部(绝对值低点)较前一波绿柱底部抬高(即绿柱长度变短,下跌缩量)。 随后出现红柱确认时买入。 5. 特殊形态与过滤条件 为提高胜率,系统需识别以下复合形态: 零轴水下金叉后的拒绝死叉: MACD在0轴下方金叉后,股价小幅调整导致快慢线黏合,但并未形成死叉(或形成失败的死叉),随后红柱再次加长。这是洗盘结束、主升浪启动的信号。 成交量验证: 红柱放大时,需要成交量同步放大的确认。若红柱放大但缩量,信号的可靠性降低。 双线联合形态: 零轴上,DIF与DEA线粘合后再次分离向上发散,同时红柱伸长,是加速上涨信号 */ import ( "fmt" "math" "strings" "git.apinb.com/bsm-sdk/core/utils" "git.apinb.com/quant/gostock/internal/impl" "git.apinb.com/quant/gostock/internal/models" talib "github.com/markcheno/go-talib" ) const ( defaultMacdFast = 12 defaultMacdSlow = 26 defaultMacdSignal = 9 ) var ( desc []string ) type MacdResult struct { Score int Val float64 Desc string } // RunMacd 根据MACD红绿柱及价量关系,对当前标的进行打分与描述 func RunMacd(code string) *MacdResult { args, _, err := GetArgConfig(code) if err != nil { return &MacdResult{Score: -1, Desc: "MACD参数错误!"} } fast := args.EmaFast slow := args.EmaSlow signal := defaultMacdSignal if fast <= 0 { fast = defaultMacdFast } if slow <= 0 { slow = defaultMacdSlow } // 为了有足够的数据观察柱体形态,这里多取一些K线 limit := slow * 6 if limit < 200 { limit = 200 } var closes []float64 var vols []float64 impl.DBService.Model(models.StockDaily{}). Where("ts_code = ?", code). Order("trade_date desc"). Limit(limit). Pluck("close", &closes) impl.DBService.Model(models.StockDaily{}). Where("ts_code = ?", code). Order("trade_date desc"). Limit(limit). Pluck("vol", &vols) if len(closes) < slow+signal+5 { return &MacdResult{Score: -1, Desc: "MACD 数据不足"} } closes = ReverseSlice(closes) if len(vols) >= len(closes) { vols = vols[:len(closes)] vols = ReverseSlice(vols) } else { // 成交量不足时不做成交量验证 vols = nil } macdLine, signalLine, hist := talib.Macd(closes, fast, slow, signal) n := len(hist) if n < 5 { return &MacdResult{Score: -1, Desc: "MACD 计算结果不足"} } lastIdx := n - 1 prevIdx := n - 2 lastHist := hist[lastIdx] prevHist := hist[prevIdx] lastMacd := macdLine[lastIdx] lastSignal := signalLine[lastIdx] macdVal := utils.FloatRound(lastHist, 4) score := -1 // 3.1 基础买入信号:绿柱转红柱,零轴下方或附近 if prevHist < 0 && lastHist > 0 && lastMacd <= 0 { score = maxInt(score, 1) desc = append(desc, "MACD红绿柱反转:由绿转红,零轴下方/附近") } // 3.2 趋势跟随买入:红柱连续伸长,DIF/DEA在零轴上方 if lastMacd > 0 && lastSignal > 0 && isGrowingPositive(hist, 3) { score = maxInt(score, 2) desc = append(desc, "MACD红柱连续伸长,DIF/DEA零轴上方,趋势跟随买入信号") } // 3.4 底背离买入:价格新低但绿柱底部抬高,随后红柱确认 if lastHist > 0 && hasBottomDivergence(closes, hist) { score = maxInt(score, 2) desc = append(desc, "MACD价格新低但绿柱底部抬高,后续红柱确认") } // 5.1 零轴下金叉后的拒绝死叉:金叉发生在零轴下方,之后未形成有效死叉,红柱再次放大 if lastHist > 0 && isGrowingPositive(hist, 2) && hasGoldenCrossRejection(macdLine, signalLine, hist) { score = maxInt(score, 3) desc = append(desc, "MACD零轴下金叉后拒绝死叉,红柱再次放大") } // 成交量验证:红柱放大但缩量,降低一次评分 if score > 0 && vols != nil && lastHist > 0 && isGrowingPositive(hist, 2) { if !isVolumeConfirmed(vols) { score-- if score <= 0 { score = -1 } return &MacdResult{Score: score, Val: macdVal, Desc: "MACD红柱放大但成交量未同步放大,信号可靠性降低"} } } if score == -1 { if lastHist <= 0 { return &MacdResult{Score: -1, Val: macdVal, Desc: fmt.Sprintf("MACD未出现明确多头信号,hist=%.4f", lastHist)} } } if score > 0 { return &MacdResult{Score: score, Val: macdVal, Desc: strings.Join(desc, "||")} } return &MacdResult{Score: -1, Val: macdVal, Desc: "无信号"} } // 最近k根红柱是否连续伸长(严格递增且均为正) func isGrowingPositive(hist []float64, k int) bool { n := len(hist) if k <= 1 || n < k { return false } start := n - k prev := hist[start] if prev <= 0 { return false } for i := start + 1; i < n; i++ { if hist[i] <= 0 || hist[i] <= prev { return false } prev = hist[i] } return true } // 底背离:最近两段绿柱,后一段对应的价格更低但绿柱绝对值更小 func hasBottomDivergence(closes, hist []float64) bool { n := len(hist) if n < 20 || len(closes) != n { return false } type valley struct { idx int hist float64 px float64 } var valleys []valley inNeg := false curMinIdx := -1 for i := 1; i < n-1; i++ { if hist[i] < 0 { if !inNeg { inNeg = true curMinIdx = i } if curMinIdx == -1 || hist[i] < hist[curMinIdx] { curMinIdx = i } } else if inNeg { // 负柱结束,记录一段绿柱的底部 valleys = append(valleys, valley{ idx: curMinIdx, hist: hist[curMinIdx], px: closes[curMinIdx], }) inNeg = false curMinIdx = -1 } } if inNeg && curMinIdx != -1 { valleys = append(valleys, valley{ idx: curMinIdx, hist: hist[curMinIdx], px: closes[curMinIdx], }) } if len(valleys) < 2 { return false } v1 := valleys[len(valleys)-2] v2 := valleys[len(valleys)-1] // 价格创新低,但绿柱绝对值变小(底部抬高) if v2.px >= v1.px { return false } if math.Abs(v2.hist) >= math.Abs(v1.hist) { return false } return true } // 零轴下方金叉后,未形成死叉且红柱再次放大 func hasGoldenCrossRejection(macdLine, signalLine, hist []float64) bool { n := len(macdLine) if n < 10 || len(signalLine) != n || len(hist) != n { return false } lastIdx := n - 1 goldenIdx := -1 // 在最近一段时间内(最多往前看60根),寻找最近一次位于零轴下方的金叉 for i := lastIdx; i >= 1 && i >= n-60; i-- { if macdLine[i-1] < signalLine[i-1] && macdLine[i] > signalLine[i] && macdLine[i] < 0 && signalLine[i] < 0 { goldenIdx = i break } } if goldenIdx == -1 { return false } // 金叉之后是否出现了真正的死叉?若出现则不算“拒绝死叉” for i := goldenIdx + 1; i <= lastIdx; i++ { if macdLine[i-1] > signalLine[i-1] && macdLine[i] < signalLine[i] { return false } } // 红柱再次放大 if hist[lastIdx] <= 0 { return false } if hist[lastIdx] <= hist[goldenIdx] { return false } return true } // 成交量确认:最近3根K线成交量均值应不低于之前3根 func isVolumeConfirmed(vols []float64) bool { n := len(vols) if n < 6 { // 数据不足时不强制要求放量 return true } var lastSum, prevSum float64 for i := n - 3; i < n; i++ { lastSum += vols[i] } for i := n - 6; i < n-3; i++ { prevSum += vols[i] } lastAvg := lastSum / 3 prevAvg := prevSum / 3 return lastAvg >= prevAvg } func maxInt(a, b int) int { if a > b { return a } return b }