diff --git a/close_fft_spectrum.pine b/close_fft_spectrum.pine new file mode 100644 index 0000000..8607787 --- /dev/null +++ b/close_fft_spectrum.pine @@ -0,0 +1,46 @@ +//@version=6 +indicator("Close FFT Spectrum", overlay=true, max_lines_count=500, max_labels_count=500, max_boxes_count=500) + +int windowLen = input.int(64, "FFT窗口长度", minval=16, maxval=256, step=8) +bool normalizeSpectrum = input.bool(true, "幅值归一化(除以窗口长度)") + +calcSpectrum(float[] samples, bool normalize) => + int size = array.size(samples) + float[] mags = array.new_float() + if size == 0 + mags + float norm = normalize ? float(size) : 1.0 + int freqLimit = size / 2 + if freqLimit < 1 + freqLimit := 1 + float twoPi = 2.0 * math.pi + for freq = 0 to freqLimit + float sumReal = 0.0 + float sumImag = 0.0 + for n = 0 to size - 1 + float sample = array.get(samples, size - 1 - n) + float angle = twoPi * float(freq) * float(n) / float(size) + sumReal += sample * math.cos(angle) + sumImag -= sample * math.sin(angle) + float magnitude = math.sqrt(sumReal * sumReal + sumImag * sumImag) / norm + array.push(mags, magnitude) + mags + +var float[] priceBuffer = array.new_float() +var int lastWindowSetting = na +if na(lastWindowSetting) or lastWindowSetting != windowLen + lastWindowSetting := windowLen + array.clear(priceBuffer) + +if not na(close) + if array.size(priceBuffer) >= windowLen + array.shift(priceBuffer) + array.push(priceBuffer, close) + +bool ready = array.size(priceBuffer) == windowLen +float[] spectrum = ready ? calcSpectrum(priceBuffer, normalizeSpectrum) : array.new_float() +int freqCount = array.size(spectrum) + +float dcComponent = ready and freqCount > 0 ? array.get(spectrum, 0) : na +plot(dcComponent, title="直流分量", color=color.orange, linewidth=2) + diff --git a/close_fft_spectrum_pane.pine b/close_fft_spectrum_pane.pine new file mode 100644 index 0000000..e96075f --- /dev/null +++ b/close_fft_spectrum_pane.pine @@ -0,0 +1,121 @@ +//@version=6 +indicator("Close FFT Spectrum Pane", overlay=false, max_lines_count=500, max_labels_count=500, max_boxes_count=500) + +int windowLen = input.int(64, "FFT窗口长度", minval=16, maxval=256, step=8) +int freqIndexInput = input.int(1, "观察的频率序号 (0=直流)", minval=0, maxval=256) +bool normalizeSpectrum = input.bool(true, "幅值归一化(除以窗口长度)") +int smoothLen = input.int(1, "幅值平滑长度 (1 = 不平滑)", minval=1, maxval=20) +bool showTable = input.bool(true, "显示主频表格") +int topCount = input.int(5, "主频数量", minval=1, maxval=12) + +calcSpectrum(float[] samples, bool normalize) => + int size = array.size(samples) + float[] mags = array.new_float() + if size == 0 + mags + float norm = normalize ? float(size) : 1.0 + int freqLimit = size / 2 + if freqLimit < 1 + freqLimit := 1 + float twoPi = 2.0 * math.pi + for freq = 0 to freqLimit + float sumReal = 0.0 + float sumImag = 0.0 + for n = 0 to size - 1 + float sample = array.get(samples, size - 1 - n) + float angle = twoPi * float(freq) * float(n) / float(size) + sumReal += sample * math.cos(angle) + sumImag -= sample * math.sin(angle) + float magnitude = math.sqrt(sumReal * sumReal + sumImag * sumImag) / norm + array.push(mags, magnitude) + mags + +getTopIndexes(float[] mags, int count) => + int available = array.size(mags) + int limit = count < available ? count : available + int[] results = array.new_int() + if limit == 0 + results + float[] scratch = array.copy(mags) + for rank = 0 to limit - 1 + float bestVal = na + int bestIdx = -1 + for i = 0 to array.size(scratch) - 1 + float candidate = array.get(scratch, i) + if na(candidate) + continue + if na(bestVal) or candidate > bestVal + bestVal := candidate + bestIdx := i + if bestIdx == -1 + break + array.push(results, bestIdx) + array.set(scratch, bestIdx, na) + results + +var float[] priceBuffer = array.new_float() +var int lastWindowSetting = na +if na(lastWindowSetting) or lastWindowSetting != windowLen + lastWindowSetting := windowLen + array.clear(priceBuffer) + +if not na(close) + if array.size(priceBuffer) >= windowLen + array.shift(priceBuffer) + array.push(priceBuffer, close) + +bool ready = array.size(priceBuffer) == windowLen +float[] spectrum = ready ? calcSpectrum(priceBuffer, normalizeSpectrum) : array.new_float() +int freqCount = array.size(spectrum) + +int clampedFreqIdx = freqCount > 0 ? math.min(freqIndexInput, freqCount - 1) : 0 +float paneMagnitude = ready and freqCount > 0 ? array.get(spectrum, clampedFreqIdx) : na +float panePlotted = smoothLen > 1 ? ta.sma(paneMagnitude, smoothLen) : paneMagnitude +plot(panePlotted, title="频率幅值", color=color.blue, linewidth=2) + +var table spectrumTable = na +var int lastTableRows = na +if showTable + if na(spectrumTable) or na(lastTableRows) or lastTableRows != topCount + 1 + if not na(spectrumTable) + table.delete(spectrumTable) + spectrumTable := table.new(position.top_right, 3, topCount + 1, bgcolor=color.new(color.black, 80), frame_color=color.new(color.gray, 60)) + lastTableRows := topCount + 1 +else if not na(spectrumTable) + table.delete(spectrumTable) + spectrumTable := na + lastTableRows := na + +if showTable and not na(spectrumTable) + color headerColor = color.new(color.white, 0) + color valueColor = color.new(color.aqua, 0) + color periodColor = color.new(color.silver, 0) + table.cell(spectrumTable, 0, 0, "频率k", text_color=headerColor, text_halign=text.align_center) + table.cell(spectrumTable, 1, 0, "幅值", text_color=headerColor, text_halign=text.align_center) + table.cell(spectrumTable, 2, 0, "周期(Bar)", text_color=headerColor, text_halign=text.align_center) + if ready and freqCount > 0 + int[] leaders = getTopIndexes(spectrum, topCount) + int leaderCount = array.size(leaders) + for row = 0 to topCount - 1 + int tableRow = row + 1 + if row < leaderCount + int freqIdx = array.get(leaders, row) + float amp = array.get(spectrum, freqIdx) + float period = freqIdx == 0 ? na : float(windowLen) / float(freqIdx) + float rounded = na(period) ? na : math.round(period * 100.0) / 100.0 + string freqText = str.tostring(freqIdx) + string ampText = str.tostring(amp, format.mintick) + string periodText = na(rounded) ? "∞" : str.tostring(rounded, format.mintick) + table.cell(spectrumTable, 0, tableRow, freqText, text_color=headerColor) + table.cell(spectrumTable, 1, tableRow, ampText, text_color=valueColor) + table.cell(spectrumTable, 2, tableRow, periodText, text_color=periodColor) + else + table.cell(spectrumTable, 0, tableRow, "-", text_color=headerColor) + table.cell(spectrumTable, 1, tableRow, "-", text_color=valueColor) + table.cell(spectrumTable, 2, tableRow, "-", text_color=periodColor) + else + for row = 1 to topCount + table.cell(spectrumTable, 0, row, "-", text_color=headerColor) + table.cell(spectrumTable, 1, row, "-", text_color=valueColor) + table.cell(spectrumTable, 2, row, "-", text_color=periodColor) + diff --git a/支撑压力位_优化版.pine b/支撑压力位_优化版.pine index acc7710..ad2603a 100644 --- a/支撑压力位_优化版.pine +++ b/支撑压力位_优化版.pine @@ -40,6 +40,19 @@ weightVolume = input.float(20, "成交量权重 %", minval=0, maxval=100, weightRecency = input.float(10, "时效性权重 %", minval=0, maxval=100, group=strengthGroup) reactionBars = input.int(5, "反应检测K线数", minval=1, maxval=20, group=strengthGroup, tooltip="检测触碰后多少根K线的价格反应") +// EMA设置 +emaGroup = "EMA线" +showEMA = input.bool(true, "显示EMA", group=emaGroup) +ema6 = input.int(6, "EMA周期1", minval=1, group=emaGroup) +ema12 = input.int(12, "EMA周期2", minval=1, group=emaGroup) +ema24 = input.int(24, "EMA周期3", minval=1, group=emaGroup) +ema52 = input.int(52, "EMA周期4", minval=1, group=emaGroup) +emaWidth = input.int(2, "EMA线宽", minval=1, maxval=5, group=emaGroup) +color6 = input.color(#FF6B9D, "EMA6色", group=emaGroup) +color12 = input.color(#C44569, "EMA12色", group=emaGroup) +color24 = input.color(#FFA502, "EMA24色", group=emaGroup) +color52 = input.color(#26A69A, "EMA52色", group=emaGroup) + // 显示设置 displayGroup = "显示设置" maxVisibleLevels = input.int(12, "最多显示水平数", minval=1, maxval=50, group=displayGroup) @@ -87,6 +100,12 @@ atrValue = ta.atr(atrLength) clusterTolerance = atrValue * clusterTolATR avgVolume = ta.sma(volume, 50) +// 计算EMA线 +ema6Value = showEMA ? ta.ema(close, ema6) : na +ema12Value = showEMA ? ta.ema(close, ema12) : na +ema24Value = showEMA ? ta.ema(close, ema24) : na +ema52Value = showEMA ? ta.ema(close, ema52) : na + // ========== 辅助函数 ========== // // 标签大小转换 @@ -460,6 +479,13 @@ if not na(pivotLow) f_cleanup_old_levels() f_draw_levels() +// 绘制EMA线 +plot(showEMA ? ema6Value : na, "EMA6", color=color6, linewidth=emaWidth, style=plot.style_line) +plot(showEMA ? ema12Value : na, "EMA12", color=color12, linewidth=emaWidth, style=plot.style_line) +plot(showEMA ? ema24Value : na, "EMA24", color=color24, linewidth=emaWidth, style=plot.style_line) +plot(showEMA ? ema52Value : na, "EMA52", color=color52, linewidth=emaWidth, style=plot.style_line) + + // 计算最近的支撑和压力 var float nearestSupport = na var float nearestResistance = na diff --git a/裸K趋势反转识别.pine b/裸K趋势反转识别.pine new file mode 100644 index 0000000..6155d86 --- /dev/null +++ b/裸K趋势反转识别.pine @@ -0,0 +1,213 @@ +//@version=6 +indicator(title="裸K趋势反转识别", shorttitle="Naked K Reversal", overlay=true, max_labels_count=500) + +// ========================= 输入参数 ========================= +// 基础设置 +showLabels = input.bool(true, "显示信号标记") +showBackground = input.bool(true, "显示背景着色") +showAlerts = input.bool(true, "启用告警") + +// 裸K形态参数 +pinbarThreshold = input.float(0.6, "PinBar阈值(0.6-0.8)", minval=0.3, maxval=0.9, step=0.05) +insideBarThreshold = input.float(0.8, "内包线阈值(0.7-0.9)", minval=0.5, maxval=0.95, step=0.05) +engulfingThreshold = input.float(0.5, "吞没线阈值(0.4-0.7)", minval=0.3, maxval=0.8, step=0.05) + +// 趋势确认参数 +trendConfirmationBars = input.int(3, "趋势确认根数", minval=1, maxval=10) +reversalConfirmationBars = input.int(2, "反转确认根数", minval=1, maxval=5) + +// 成交量确认 +useVolumeConfirmation = input.bool(true, "使用成交量确认") +volumeMultiplier = input.float(1.2, "成交量倍数", minval=1.0, maxval=3.0, step=0.1) + +// 灵敏度 +sensitivity = input.float(1.0, "灵敏度", minval=0.5, maxval=2.0, step=0.1) + +// 颜色设置 +colBullish = color.new(color.green, 80) +colBearish = color.new(color.red, 80) +colNeutral = color.new(color.gray, 85) + +// ========================= 裸K形态识别 ========================= +// 1. PinBar (锤子线/上吊线) +barRange = high - low +bodyRange = math.abs(close - open) +upperShadow = high - math.max(open, close) +lowerShadow = math.min(open, close) - low + +isPinBar = barRange > 0 and bodyRange / barRange < pinbarThreshold +isBullishPinBar = isPinBar and lowerShadow > upperShadow * 2 +isBearishPinBar = isPinBar and upperShadow > lowerShadow * 2 + +// 2. 内包线 (Inside Bar) +isInsideBar = high <= high[1] and low >= low[1] +insideBarStrength = (high[1] - low[1]) > 0 ? (high - low) / (high[1] - low[1]) : 1 +isStrongInsideBar = isInsideBar and insideBarStrength < insideBarThreshold + +// 3. 吞没线 (Engulfing) +isBullishEngulfing = close > open and close[1] < open[1] and + close > open[1] and open < close[1] and + bodyRange > bodyRange[1] * engulfingThreshold + +isBearishEngulfing = close < open and close[1] > open[1] and + close < open[1] and open > close[1] and + bodyRange > bodyRange[1] * engulfingThreshold + +// 4. 孕线 (Harami) +isBullishHarami = close > open and close[1] < open[1] and + high < high[1] and low > low[1] and + bodyRange < bodyRange[1] * 0.7 + +isBearishHarami = close < open and close[1] > open[1] and + high < high[1] and low > low[1] and + bodyRange < bodyRange[1] * 0.7 + +// 5. 乌云盖顶 (Dark Cloud Cover) +isDarkCloud = close < open and close[1] > open[1] and + open > close[1] and close < (open[1] + close[1]) / 2 + +// 6. 刺透形态 (Piercing Pattern) +isPiercing = close > open and close[1] < open[1] and + open < low[1] and close > (open[1] + close[1]) / 2 + +// ========================= 趋势判断 ========================= +// 简单移动平均线趋势判断 +emaFast = ta.ema(close, 10) +emaSlow = ta.ema(close, 20) + +isUptrend = emaFast > emaSlow and close > emaSlow +isDowntrend = emaFast < emaSlow and close < emaSlow + +// 趋势强度 +var int uptrendCount = 0 +var int downtrendCount = 0 + +uptrendCount := isUptrend ? uptrendCount + 1 : 0 +downtrendCount := isDowntrend ? downtrendCount + 1 : 0 + +uptrendConfirmed = uptrendCount >= trendConfirmationBars +downtrendConfirmed = downtrendCount >= trendConfirmationBars + +// ========================= 成交量确认 ========================= +volumeMa = ta.sma(volume, 20) +volumeConfirmed = not useVolumeConfirmation or volume > volumeMa * volumeMultiplier + +// ========================= 反转信号生成 ========================= +// 看涨反转信号 +bullishReversalSignal = (isBullishPinBar or isBullishEngulfing or isBullishHarami or isPiercing) and + downtrendConfirmed and volumeConfirmed + +// 看跌反转信号 +bearishReversalSignal = (isBearishPinBar or isBearishEngulfing or isBearishHarami or isDarkCloud) and + uptrendConfirmed and volumeConfirmed + +// 反转确认计数 +var int bullishReversalCount = 0 +var int bearishReversalCount = 0 + +bullishReversalCount := bullishReversalSignal ? bullishReversalCount + 1 : 0 +bearishReversalCount := bearishReversalSignal ? bearishReversalCount + 1 : 0 + +bullishReversalConfirmed = bullishReversalCount >= reversalConfirmationBars +bearishReversalConfirmed = bearishReversalCount >= reversalConfirmationBars + +// ========================= 状态机 ========================= +// 0: 无趋势, 1: 上涨趋势, -1: 下跌趋势, 2: 反转中 +var int marketState = 0 +prevState = nz(marketState[1], 0) + +// 状态转换逻辑 +int newState = prevState + +if prevState == 0 // 无趋势状态 + if uptrendConfirmed + newState := 1 + else if downtrendConfirmed + newState := -1 + +else if prevState == 1 // 上涨趋势 + if bearishReversalConfirmed + newState := -1 + else if not uptrendConfirmed + newState := 0 + +else if prevState == -1 // 下跌趋势 + if bullishReversalConfirmed + newState := 1 + else if not downtrendConfirmed + newState := 0 + +marketState := barstate.isconfirmed ? newState : nz(marketState[1], prevState) + +// ========================= 可视化 ========================= +// 背景着色 +bgColor = marketState == 1 ? colBullish : marketState == -1 ? colBearish : colNeutral +bgcolor(showBackground ? bgColor : na, title="市场状态背景") + +// 裸K形态标记 +plotshape(showLabels and isBullishPinBar, title="看涨PinBar", style=shape.triangleup, + location=location.belowbar, color=color.green, size=size.small) +plotshape(showLabels and isBearishPinBar, title="看跌PinBar", style=shape.triangledown, + location=location.abovebar, color=color.red, size=size.small) + +plotshape(showLabels and isBullishEngulfing, title="看涨吞没", style=shape.circle, + location=location.belowbar, color=color.lime, size=size.small) +plotshape(showLabels and isBearishEngulfing, title="看跌吞没", style=shape.circle, + location=location.abovebar, color=color.maroon, size=size.small) + +plotshape(showLabels and isStrongInsideBar, title="内包线", style=shape.diamond, + location=location.top, color=color.orange, size=size.small) + +// 反转信号标记 +plotshape(showLabels and bullishReversalConfirmed, title="看涨反转确认", + style=shape.labelup, location=location.belowbar, color=color.green, + text="↑", textcolor=color.white, size=size.normal) + +plotshape(showLabels and bearishReversalConfirmed, title="看跌反转确认", + style=shape.labeldown, location=location.abovebar, color=color.red, + text="↓", textcolor=color.white, size=size.normal) + +// 趋势线 +plot(emaFast, color=color.new(color.blue, 0), linewidth=1, title="EMA快线") +plot(emaSlow, color=color.new(color.orange, 0), linewidth=2, title="EMA慢线") + +// ========================= 告警系统 ========================= +alertcondition(showAlerts and bullishReversalConfirmed, title="看涨反转信号", + message="裸K看涨反转信号确认") +alertcondition(showAlerts and bearishReversalConfirmed, title="看跌反转信号", + message="裸K看跌反转信号确认") + +// ========================= 信息显示 ========================= +// 在图表上显示当前状态 +var table infoTable = table.new(position.top_right, 2, 6, bgcolor=color.new(color.gray, 90), + border_width=1) + +if barstate.islast + table.cell(infoTable, 0, 0, "裸K趋势反转识别", text_color=color.white, + bgcolor=color.new(color.blue, 70)) + table.cell(infoTable, 1, 0, "状态", text_color=color.white, + bgcolor=color.new(color.blue, 70)) + + table.cell(infoTable, 0, 1, "市场状态") + table.cell(infoTable, 1, 1, marketState == 1 ? "上涨趋势" : marketState == -1 ? "下跌趋势" : "震荡", + text_color=marketState == 1 ? color.green : marketState == -1 ? color.red : color.gray) + + table.cell(infoTable, 0, 2, "趋势强度") + table.cell(infoTable, 1, 2, str.tostring(math.max(uptrendCount, downtrendCount))) + + table.cell(infoTable, 0, 3, "看涨信号") + table.cell(infoTable, 1, 3, bullishReversalConfirmed ? "确认" : "等待", + text_color=bullishReversalConfirmed ? color.green : color.gray) + + table.cell(infoTable, 0, 4, "看跌信号") + table.cell(infoTable, 1, 4, bearishReversalConfirmed ? "确认" : "等待", + text_color=bearishReversalConfirmed ? color.red : color.gray) + + table.cell(infoTable, 0, 5, "成交量确认") + table.cell(infoTable, 1, 5, volumeConfirmed ? "是" : "否", + text_color=volumeConfirmed ? color.green : color.orange) + +// ========================= 使用说明 ========================= +// 本脚本专注于裸K形态识别趋势反转信号 +// 主要识别形态:PinBar、吞没线、内包线、孕线、乌云盖顶、刺透形态 +// 结合趋势确认和成交量验证,提高信号可靠性