添加新的识别
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+213
@@ -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、吞没线、内包线、孕线、乌云盖顶、刺透形态
|
||||
// 结合趋势确认和成交量验证,提高信号可靠性
|
||||
Reference in New Issue
Block a user