From a0dc3ba147cab30f9f1876820383710dfc9fe0ce Mon Sep 17 00:00:00 2001 From: Porter Date: Sat, 27 Sep 2025 12:20:18 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 181 +++++++++++++++++++ EMA52横线版.pine | 369 +++++++++++++++++++++++++++++++++++++++ README.md | 2 + macd_divergence.pine | 106 +++++++++++ macd_histogram_area.pine | 59 +++++++ 多周期EMA52.pine | 322 ++++++++++++++++++++++++++++++++++ 自定义MACD柱子面积.pine | 82 +++++++++ 高级MACD背驰检测.pine | 208 ++++++++++++++++++++++ 9 files changed, 1331 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 EMA52横线版.pine create mode 100644 README.md create mode 100644 macd_divergence.pine create mode 100644 macd_histogram_area.pine create mode 100644 多周期EMA52.pine create mode 100644 自定义MACD柱子面积.pine create mode 100644 高级MACD背驰检测.pine diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd15ec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,181 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore \ No newline at end of file diff --git a/EMA52横线版.pine b/EMA52横线版.pine new file mode 100644 index 0000000..8b6985d --- /dev/null +++ b/EMA52横线版.pine @@ -0,0 +1,369 @@ +//@version=6 +indicator("EMA52横线版 (HTF/LTF 最新值)", overlay=true, max_labels_count=500, max_lines_count=500) + +// ============ 输入 ============ +enableHTF = input.bool(true, "启用长级别", group="显示") +enableLTF = input.bool(true, "启用次级别", group="显示") +maxHTFInput = input.int(10, "最多显示长级别数量", minval=1, maxval=12, group="显示") +maxLTFInput = input.int(3, "最多显示次级别数量", minval=1, maxval=4, group="显示") +includeSeconds = input.bool(false, "包含秒级别(需高级套餐)", group="显示") +protectAutoscale = input.bool(true, "防拉伸: 仅在可视区间内显示", group="显示") +lookbackBars = input.int(300, "可视区间近多少根K线", minval=50, maxval=5000, group="显示") +padPercent = input.float(2.0, "可视边界留白(%)", minval=0.0, maxval=20.0, step=0.1, group="显示") + +labelBgAlpha = input.int(85, "标签背景透明(0-100)", minval=0, maxval=100, group="标注") +labelSize = input.string("tiny", "标签字号", options=["tiny","small","normal","large","huge"], group="标注") +labelOffsetBars = input.int(2, "标签右移K线的距离(根数)", minval=0, maxval=50, group="标注") + +// ============ 按线控制(可见性/颜色/宽度/样式) ============ +// 为满足 Pine 对常量标题的要求,逐一声明。 +// 长级别 +groupHTF = "按线控制 - 长级别" +hShow1 = input.bool(true, "显示 HTF1", group=groupHTF) +hColor1 = input.color(color.new(color.teal, 0), "颜色 HTF1", group=groupHTF) +hWidth1 = input.int(1, "线宽 HTF1", minval=1, maxval=4, group=groupHTF) +hStyle1 = input.string("dotted", "样式 HTF1", options=["solid","dashed","dotted"], group=groupHTF) +hShow2 = input.bool(true, "显示 HTF2", group=groupHTF) +hColor2 = input.color(color.new(color.orange, 0), "颜色 HTF2", group=groupHTF) +hWidth2 = input.int(1, "线宽 HTF2", minval=1, maxval=4, group=groupHTF) +hStyle2 = input.string("dotted", "样式 HTF2", options=["solid","dashed","dotted"], group=groupHTF) +hShow3 = input.bool(true, "显示 HTF3", group=groupHTF) +hColor3 = input.color(color.new(color.fuchsia,0), "颜色 HTF3", group=groupHTF) +hWidth3 = input.int(1, "线宽 HTF3", minval=1, maxval=4, group=groupHTF) +hStyle3 = input.string("dotted", "样式 HTF3", options=["solid","dashed","dotted"], group=groupHTF) +hShow4 = input.bool(true, "显示 HTF4", group=groupHTF) +hColor4 = input.color(color.new(color.aqua, 0), "颜色 HTF4", group=groupHTF) +hWidth4 = input.int(1, "线宽 HTF4", minval=1, maxval=4, group=groupHTF) +hStyle4 = input.string("dotted", "样式 HTF4", options=["solid","dashed","dotted"], group=groupHTF) +hShow5 = input.bool(true, "显示 HTF5", group=groupHTF) +hColor5 = input.color(color.new(color.yellow, 0), "颜色 HTF5", group=groupHTF) +hWidth5 = input.int(1, "线宽 HTF5", minval=1, maxval=4, group=groupHTF) +hStyle5 = input.string("dotted", "样式 HTF5", options=["solid","dashed","dotted"], group=groupHTF) +hShow6 = input.bool(true, "显示 HTF6", group=groupHTF) +hColor6 = input.color(color.new(color.purple, 0), "颜色 HTF6", group=groupHTF) +hWidth6 = input.int(1, "线宽 HTF6", minval=1, maxval=4, group=groupHTF) +hStyle6 = input.string("dotted", "样式 HTF6", options=["solid","dashed","dotted"], group=groupHTF) +hShow7 = input.bool(true, "显示 HTF7", group=groupHTF) +hColor7 = input.color(color.new(color.lime, 0), "颜色 HTF7", group=groupHTF) +hWidth7 = input.int(1, "线宽 HTF7", minval=1, maxval=4, group=groupHTF) +hStyle7 = input.string("dotted", "样式 HTF7", options=["solid","dashed","dotted"], group=groupHTF) +hShow8 = input.bool(true, "显示 HTF8", group=groupHTF) +hColor8 = input.color(color.new(color.red, 0), "颜色 HTF8", group=groupHTF) +hWidth8 = input.int(1, "线宽 HTF8", minval=1, maxval=4, group=groupHTF) +hStyle8 = input.string("dotted", "样式 HTF8", options=["solid","dashed","dotted"], group=groupHTF) +hShow9 = input.bool(true, "显示 HTF9", group=groupHTF) +hColor9 = input.color(color.new(color.blue, 0), "颜色 HTF9", group=groupHTF) +hWidth9 = input.int(1, "线宽 HTF9", minval=1, maxval=4, group=groupHTF) +hStyle9 = input.string("dotted", "样式 HTF9", options=["solid","dashed","dotted"], group=groupHTF) +hShow10 = input.bool(true, "显示 HTF10", group=groupHTF) +hColor10 = input.color(color.new(color.navy, 0), "颜色 HTF10", group=groupHTF) +hWidth10 = input.int(1, "线宽 HTF10", minval=1, maxval=4, group=groupHTF) +hStyle10 = input.string("dotted", "样式 HTF10", options=["solid","dashed","dotted"], group=groupHTF) +hShow11 = input.bool(true, "显示 HTF11", group=groupHTF) +hColor11 = input.color(color.new(color.maroon, 0), "颜色 HTF11", group=groupHTF) +hWidth11 = input.int(1, "线宽 HTF11", minval=1, maxval=4, group=groupHTF) +hStyle11 = input.string("dotted", "样式 HTF11", options=["solid","dashed","dotted"], group=groupHTF) +hShow12 = input.bool(true, "显示 HTF12", group=groupHTF) +hColor12 = input.color(color.new(color.silver, 0), "颜色 HTF12", group=groupHTF) +hWidth12 = input.int(1, "线宽 HTF12", minval=1, maxval=4, group=groupHTF) +hStyle12 = input.string("dotted", "样式 HTF12", options=["solid","dashed","dotted"], group=groupHTF) + +// 次级别 +groupLTF = "按线控制 - 次级别" +lShow1 = input.bool(true, "显示 LTF1", group=groupLTF) +lColor1 = input.color(color.new(color.teal, 30), "颜色 LTF1", group=groupLTF) +lWidth1 = input.int(1, "线宽 LTF1", minval=1, maxval=4, group=groupLTF) +lStyle1 = input.string("dotted", "样式 LTF1", options=["solid","dashed","dotted"], group=groupLTF) +lShow2 = input.bool(true, "显示 LTF2", group=groupLTF) +lColor2 = input.color(color.new(color.orange, 30),"颜色 LTF2", group=groupLTF) +lWidth2 = input.int(1, "线宽 LTF2", minval=1, maxval=4, group=groupLTF) +lStyle2 = input.string("dotted", "样式 LTF2", options=["solid","dashed","dotted"], group=groupLTF) +lShow3 = input.bool(true, "显示 LTF3", group=groupLTF) +lColor3 = input.color(color.new(color.fuchsia,30),"颜色 LTF3", group=groupLTF) +lWidth3 = input.int(1, "线宽 LTF3", minval=1, maxval=4, group=groupLTF) +lStyle3 = input.string("dotted", "样式 LTF3", options=["solid","dashed","dotted"], group=groupLTF) +lShow4 = input.bool(true, "显示 LTF4", group=groupLTF) +lColor4 = input.color(color.new(color.aqua, 30),"颜色 LTF4", group=groupLTF) +lWidth4 = input.int(1, "线宽 LTF4", minval=1, maxval=4, group=groupLTF) +lStyle4 = input.string("dotted", "样式 LTF4", options=["solid","dashed","dotted"], group=groupLTF) + +// 样式字符串到 line.style 的映射 +f_lineStyle(string s) => s == "solid" ? line.style_solid : s == "dashed" ? line.style_dashed : line.style_dotted + +// 取对应序号的配置 +f_htfShow(int i) => i == 0 ? hShow1 : i == 1 ? hShow2 : i == 2 ? hShow3 : i == 3 ? hShow4 : i == 4 ? hShow5 : i == 5 ? hShow6 : i == 6 ? hShow7 : i == 7 ? hShow8 : i == 8 ? hShow9 : i == 9 ? hShow10 : i == 10 ? hShow11 : hShow12 +f_htfColor(int i) => i == 0 ? hColor1 : i == 1 ? hColor2 : i == 2 ? hColor3 : i == 3 ? hColor4 : i == 4 ? hColor5 : i == 5 ? hColor6 : i == 6 ? hColor7 : i == 7 ? hColor8 : i == 8 ? hColor9 : i == 9 ? hColor10 : i == 10 ? hColor11 : hColor12 +f_htfWidth(int i) => i == 0 ? hWidth1 : i == 1 ? hWidth2 : i == 2 ? hWidth3 : i == 3 ? hWidth4 : i == 4 ? hWidth5 : i == 5 ? hWidth6 : i == 6 ? hWidth7 : i == 7 ? hWidth8 : i == 8 ? hWidth9 : i == 9 ? hWidth10 : i == 10 ? hWidth11 : hWidth12 +f_htfStyle(int i) => f_lineStyle(i == 0 ? hStyle1 : i == 1 ? hStyle2 : i == 2 ? hStyle3 : i == 3 ? hStyle4 : i == 4 ? hStyle5 : i == 5 ? hStyle6 : i == 6 ? hStyle7 : i == 7 ? hStyle8 : i == 8 ? hStyle9 : i == 9 ? hStyle10 : i == 10 ? hStyle11 : hStyle12) + +f_ltfShow(int i) => i == 0 ? lShow1 : i == 1 ? lShow2 : i == 2 ? lShow3 : lShow4 +f_ltfColor(int i) => i == 0 ? lColor1 : i == 1 ? lColor2 : i == 2 ? lColor3 : lColor4 +f_ltfWidth(int i) => i == 0 ? lWidth1 : i == 1 ? lWidth2 : i == 2 ? lWidth3 : lWidth4 +f_ltfStyle(int i) => f_lineStyle(i == 0 ? lStyle1 : i == 1 ? lStyle2 : i == 2 ? lStyle3 : lStyle4) + +// ============ 工具 ============ +f_strToSize(string s) => switch s + "tiny" => size.tiny + "small" => size.small + "normal" => size.normal + "large" => size.large + => size.huge + +var color[] PALETTE = array.new_color() +if barstate.isfirst and array.size(PALETTE) == 0 + array.push(PALETTE, color.new(color.teal, 0)) + array.push(PALETTE, color.new(color.orange, 0)) + array.push(PALETTE, color.new(color.fuchsia,0)) + array.push(PALETTE, color.new(color.aqua, 0)) + array.push(PALETTE, color.new(color.yellow, 0)) + array.push(PALETTE, color.new(color.purple, 0)) + array.push(PALETTE, color.new(color.lime, 0)) + array.push(PALETTE, color.new(color.red, 0)) + array.push(PALETTE, color.new(color.blue, 0)) + array.push(PALETTE, color.new(color.navy, 0)) + array.push(PALETTE, color.new(color.maroon, 0)) + array.push(PALETTE, color.new(color.silver, 0)) + +f_paletteColor(int idx) => + sz = math.max(1, array.size(PALETTE)) + array.get(PALETTE, idx % sz) + +f_isSecondsTf(string tf) => + ln = str.length(tf) + ln > 0 and str.substring(tf, ln - 1, ln) == 'S' + +f_prettyTf(string tf) => + s = timeframe.in_seconds(tf) + string out = tf + if not na(s) + if s < 3600 + mins = math.max(1, math.round(s / 60)) + out := str.tostring(mins) + 'm' + else if s % 3600 == 0 and s < 86400 + hrs = math.round(s / 3600) + out := str.tostring(hrs) + 'h' + else if s % 86400 == 0 and s < 7 * 86400 + days = math.round(s / 86400) + out := str.tostring(days) + 'D' + else if s % (7 * 86400) == 0 and s < 30 * 86400 + weeks = math.round(s / (7 * 86400)) + out := str.tostring(weeks) + 'W' + else + out := tf + else + out := tf + out + +// ============ 周期间构建 ============ +var string[] ALL_RES = array.new_string() +if barstate.isfirst and array.size(ALL_RES) == 0 + array.push(ALL_RES, "1S") + array.push(ALL_RES, "5S") + array.push(ALL_RES, "15S") + array.push(ALL_RES, "30S") + array.push(ALL_RES, "1") + array.push(ALL_RES, "3") + array.push(ALL_RES, "5") + array.push(ALL_RES, "15") + array.push(ALL_RES, "30") + array.push(ALL_RES, "45") + array.push(ALL_RES, "60") + array.push(ALL_RES, "120") + array.push(ALL_RES, "240") + array.push(ALL_RES, "360") + array.push(ALL_RES, "480") + array.push(ALL_RES, "720") + array.push(ALL_RES, "D") + array.push(ALL_RES, "2D") + array.push(ALL_RES, "3D") + array.push(ALL_RES, "W") + array.push(ALL_RES, "2W") + array.push(ALL_RES, "M") + array.push(ALL_RES, "3M") + +curResStr = timeframe.period +curResSec = timeframe.in_seconds(curResStr) + +var string[] htfRes = array.new_string() +var string[] ltfRes = array.new_string() +array.clear(htfRes) +array.clear(ltfRes) +for i = 0 to array.size(ALL_RES) - 1 + res = array.get(ALL_RES, i) + if not includeSeconds and f_isSecondsTf(res) + continue + rsec = timeframe.in_seconds(res) + if na(rsec) + continue + if rsec > curResSec + array.push(htfRes, res) + else if rsec < curResSec + array.push(ltfRes, res) + +var string[] ltfSel = array.new_string() +array.clear(ltfSel) +ltfsz = array.size(ltfRes) +if ltfsz > 0 + take = math.min(maxLTFInput, 4, ltfsz) + for k = 0 to take - 1 + array.push(ltfSel, array.get(ltfRes, ltfsz - 1 - k)) + +htfCountSelected = math.min(array.size(htfRes), maxHTFInput, 12) +ltfCountSelected = math.min(array.size(ltfSel), 4) + +// ============ 计算最新 EMA52 ============ +var float[] htfVals = array.new_float() +var float[] ltfVals = array.new_float() +if barstate.isfirst + for _i = 0 to 11 + array.push(htfVals, na) + for _j = 0 to 3 + array.push(ltfVals, na) + +for i = 0 to 11 + float val = na + if enableHTF and i < htfCountSelected + tfStr = array.get(htfRes, i) + val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(htfVals, i, val) + +for i = 0 to 3 + float val = na + if enableLTF and i < ltfCountSelected + tfStr = array.get(ltfSel, i) + val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(ltfVals, i, val) + +// ============ 可视区间 ============ +rangeHi = ta.highest(high, lookbackBars) +rangeLo = ta.lowest(low, lookbackBars) +rangeSpan = rangeHi - rangeLo +pad = rangeSpan * padPercent * 0.01 +minY = rangeLo - pad +maxY = rangeHi + pad + +f_inRange(series float v) => protectAutoscale ? (v >= minY and v <= maxY) : true + +// ============ 绘制水平线与标签(仅最新值) ============ +var line[] htfLines = array.new_line() +var line[] ltfLines = array.new_line() +var label[] htfLabels = array.new_label() +var label[] ltfLabels = array.new_label() +var string lastTicker = na +var string lastTf = na +if barstate.isfirst + for _i = 0 to 11 + array.push(htfLines, na) + array.push(htfLabels, na) + for _j = 0 to 3 + array.push(ltfLines, na) + array.push(ltfLabels, na) + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +// 清理:品种或周期变化 +symbolChanged = lastTicker != syminfo.tickerid +tfChanged = lastTf != timeframe.period +if symbolChanged or tfChanged + for i = 0 to 11 + l = array.get(htfLines, i) + if not na(l) + line.delete(l) + array.set(htfLines, i, na) + lb = array.get(htfLabels, i) + if not na(lb) + label.delete(lb) + array.set(htfLabels, i, na) + for i = 0 to 3 + l = array.get(ltfLines, i) + if not na(l) + line.delete(l) + array.set(ltfLines, i, na) + lb = array.get(ltfLabels, i) + if not na(lb) + label.delete(lb) + array.set(ltfLabels, i, na) + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +// 更新:在最后一根柱上画从最左端到最右端的水平线(仅一段),并在右侧放标签 +if barstate.islast + // 长级别 + for i = 0 to 11 + // 颜色/线宽/样式来自按线控制面板 + col = f_htfColor(i) + v = array.get(htfVals, i) + show = enableHTF and i < htfCountSelected and f_htfShow(i) and not na(v) and f_inRange(v) + ln = array.get(htfLines, i) + lb = array.get(htfLabels, i) + if show + x1 = bar_index - lookbackBars + x2 = bar_index + labelOffsetBars + if na(ln) + ln := line.new(x1, v, x2, v, xloc=xloc.bar_index, extend=extend.none, color=col, width=f_htfWidth(i), style=f_htfStyle(i)) + array.set(htfLines, i, ln) + else + line.set_xy1(ln, x1, v) + line.set_xy2(ln, x2, v) + line.set_color(ln, col) + line.set_width(ln, f_htfWidth(i)) + line.set_style(ln, f_htfStyle(i)) + txt = f_prettyTf(array.get(htfRes, i)) + ' ' + str.tostring(v, format.mintick) + if na(lb) + lb := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) + array.set(htfLabels, i, lb) + else + label.set_xy(lb, bar_index + labelOffsetBars, v) + label.set_text(lb, txt) + label.set_textcolor(lb, col) + label.set_color(lb, color.new(col, labelBgAlpha)) + label.set_style(lb, label.style_label_left) + label.set_size(lb, f_strToSize(labelSize)) + else + if not na(ln) + line.delete(ln) + array.set(htfLines, i, na) + if not na(lb) + label.delete(lb) + array.set(htfLabels, i, na) + + // 次级别 + for i = 0 to 3 + base = f_ltfColor(i) + col = base + v = array.get(ltfVals, i) + show = enableLTF and i < ltfCountSelected and f_ltfShow(i) and not na(v) and f_inRange(v) + ln = array.get(ltfLines, i) + lb = array.get(ltfLabels, i) + if show + x1 = bar_index - lookbackBars + x2 = bar_index + labelOffsetBars + if na(ln) + ln := line.new(x1, v, x2, v, xloc=xloc.bar_index, extend=extend.none, color=col, width=f_ltfWidth(i), style=f_ltfStyle(i)) + array.set(ltfLines, i, ln) + else + line.set_xy1(ln, x1, v) + line.set_xy2(ln, x2, v) + line.set_color(ln, col) + line.set_width(ln, f_ltfWidth(i)) + line.set_style(ln, f_ltfStyle(i)) + txt = f_prettyTf(array.get(ltfSel, i)) + ' ' + str.tostring(v, format.mintick) + if na(lb) + lb := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10), size=f_strToSize(labelSize)) + array.set(ltfLabels, i, lb) + else + label.set_xy(lb, bar_index + labelOffsetBars, v) + label.set_text(lb, txt) + label.set_textcolor(lb, col) + label.set_color(lb, color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10)) + label.set_style(lb, label.style_label_left) + label.set_size(lb, f_strToSize(labelSize)) + else + if not na(ln) + line.delete(ln) + array.set(ltfLines, i, na) + if not na(lb) + label.delete(lb) + array.set(ltfLabels, i, na) + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae353d3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tradingview +TradingView scripts diff --git a/macd_divergence.pine b/macd_divergence.pine new file mode 100644 index 0000000..a821a86 --- /dev/null +++ b/macd_divergence.pine @@ -0,0 +1,106 @@ +// MACD与价格背驰检测指标 +// 作者: Claude AI +// 版本: 1.0 + +//@version=5 +indicator("MACD背驰检测", shorttitle="MACD背驰", overlay=true) + +// MACD参数 +fast_length = input.int(12, "MACD快线长度", minval=1) +slow_length = input.int(26, "MACD慢线长度", minval=1) +signal_length = input.int(9, "MACD信号线长度", minval=1) + +// 背驰检测参数 +lookback = input.int(10, "回溯检测周期", minval=5) +threshold = input.float(0.5, "背驰阈值", minval=0.1, step=0.1) + +// 计算MACD +[macdLine, signalLine, histLine] = ta.macd(close, fast_length, slow_length, signal_length) + +// 寻找价格的高点和低点 +price_high = ta.highest(high, lookback) +price_low = ta.lowest(low, lookback) + +// 判断当前柱是否是局部高点或低点 +is_price_high = high == price_high and high > high[1] and high > high[2] +is_price_low = low == price_low and low < low[1] and low < low[2] + +// 保存价格高点和低点及其对应的MACD值 +var float last_price_high = na +var float last_price_low = na +var float last_macd_at_price_high = na +var float last_macd_at_price_low = na + +// 寻找MACD的高点和低点 +macd_high = ta.highest(macdLine, lookback) +macd_low = ta.lowest(macdLine, lookback) + +// ----- 背驰检测逻辑 ----- + +// 顶背驰:价格创新高,但MACD没有创新高 +bearish_divergence = false +if is_price_high + // 保存当前高点及对应的MACD值 + if not na(last_price_high) and high > last_price_high and macdLine < last_macd_at_price_high + bearish_divergence := true + + last_price_high := high + last_macd_at_price_high := macdLine + +// 底背驰:价格创新低,但MACD没有创新低 +bullish_divergence = false +if is_price_low + // 保存当前低点及对应的MACD值 + if not na(last_price_low) and low < last_price_low and macdLine > last_macd_at_price_low + bullish_divergence := true + + last_price_low := low + last_macd_at_price_low := macdLine + +// ----- 标注背驰 ----- + +// 顶背驰标注 +plotshape( + bearish_divergence, + title="顶背驰", + location=location.abovebar, + color=color.red, + style=shape.triangledown, + size=size.normal, + text="顶背驰") + +// 底背驰标注 +plotshape( + bullish_divergence, + title="底背驰", + location=location.belowbar, + color=color.green, + style=shape.triangleup, + size=size.normal, + text="底背驰") + +// ----- 辅助指标 ----- + +// 在单独窗口绘制MACD +plot(macdLine, "MACD", color=color.blue, display=display.none) +plot(signalLine, "信号线", color=color.orange, display=display.none) +plot(histLine, "柱状图", color=histLine >= 0 ? color.green : color.red, style=plot.style_histogram, display=display.none) + +// 显示背驰信息 +var label divergenceLabel = na +if barstate.islast + label.delete(divergenceLabel) + if bearish_divergence + divergenceLabel := label.new( + bar_index, high, + text="顶背驰警告!\n价格创新高但MACD未跟进", + color=color.red, + style=label.style_label_down, + textcolor=color.white) + else if bullish_divergence + divergenceLabel := label.new( + bar_index, low, + text="底背驰警告!\n价格创新低但MACD未跟进", + color=color.green, + style=label.style_label_up, + textcolor=color.white) \ No newline at end of file diff --git a/macd_histogram_area.pine b/macd_histogram_area.pine new file mode 100644 index 0000000..5107846 --- /dev/null +++ b/macd_histogram_area.pine @@ -0,0 +1,59 @@ +// MACD柱子面积计算指标 +// 作者: Claude AI +// 版本: 1.0 + +//@version=5 +indicator("MACD柱子面积", shorttitle="MACD面积", overlay=false) + +// MACD参数 +fast_length = input.int(12, "快线长度", minval=1) +slow_length = input.int(26, "慢线长度", minval=1) +signal_length = input.int(9, "信号线长度", minval=1) + +// 计算MACD +[macdLine, signalLine, histLine] = ta.macd(close, fast_length, slow_length, signal_length) + +// 初始化面积变量 +var float upArea = 0.0 +var float downArea = 0.0 + +// 检测柱子正负性并累加面积 +if histLine > 0 + upArea := upArea + histLine + // 当从负值转为正值时,重置下跌面积 + if histLine[1] < 0 + downArea := 0.0 +else if histLine < 0 + downArea := downArea + math.abs(histLine) + // 当从正值转为负值时,重置上涨面积 + if histLine[1] > 0 + upArea := 0.0 + +// 绘制结果 +plot(upArea, "上涨段面积", color=color.green, style=plot.style_area) +plot(downArea, "下跌段面积", color=color.red, style=plot.style_area) +plot(histLine, "MACD柱状图", color=histLine >= 0 ? color.green : color.red, style=plot.style_histogram) +plot(macdLine, "MACD", color=color.blue) +plot(signalLine, "信号线", color=color.orange) + +// 添加文字标签 +var label upLabel = na +var label downLabel = na + +if barstate.islast + upLabel := label.new( + bar_index, upArea, + text="上涨面积: " + str.tostring(upArea, "#.##"), + color=color.green, + style=label.style_label_down, + textcolor=color.white) + + downLabel := label.new( + bar_index, downArea, + text="下跌面积: " + str.tostring(downArea, "#.##"), + color=color.red, + style=label.style_label_up, + textcolor=color.white) + + label.delete(upLabel[1]) + label.delete(downLabel[1]) \ No newline at end of file diff --git a/多周期EMA52.pine b/多周期EMA52.pine new file mode 100644 index 0000000..ba971df --- /dev/null +++ b/多周期EMA52.pine @@ -0,0 +1,322 @@ +//@version=6 +indicator("多周期EMA52 (HTF/LTF)", overlay=true, max_labels_count=500, max_lines_count=500) + +// ======================== 配置输入 ======================== +// 主控 +enableHTF = input.bool(true, "启用长级别", group="显示") +enableLTF = input.bool(true, "启用次级别", group="显示") +maxHTFInput = input.int(10, "最多显示长级别数量", minval=1, maxval=12, group="显示") +maxLTFInput = input.int(3, "最多显示次级别数量", minval=1, maxval=4, group="显示") +includeSeconds = input.bool(false, "包含秒级别(需高级套餐)", group="显示") +protectAutoscale = input.bool(true, "防拉伸: 仅绘制可视区间内", group="显示") +lookbackBars = input.int(300, "可视区间近多少根K线", minval=50, maxval=5000, group="显示") +padPercent = input.float(2.0, "可视边界留白(%)", minval=0.0, maxval=20.0, step=0.1, group="显示") + +// 视觉(虚线效果) +dashLenHTF = input.int(6, "长级别虚线段长度", minval=1, maxval=50, group="线型") +dashGapHTF = input.int(4, "长级别虚线间隔", minval=1, maxval=50, group="线型") +dashLenLTF = input.int(2, "次级别虚线段长度", minval=1, maxval=50, group="线型") +dashGapLTF = input.int(3, "次级别虚线间隔", minval=1, maxval=50, group="线型") +lineWidthHTF = input.int(1, "长级别线宽", minval=1, maxval=4, group="线型") +lineWidthLTF = input.int(1, "次级别线宽", minval=1, maxval=4, group="线型") +labelBgAlpha = input.int(85, "标签背景透明(0-100)", minval=0, maxval=100, group="标注") +labelSize = input.string("tiny", "标签字号", options=["tiny","small","normal","large","huge"], group="标注") +labelOffsetBars = input.int(2, "标签右移K线的距离(根数)", minval=0, maxval=50, group="标注") + +// 常量上限(编译期固定,用于生成固定数量的 plot 位) +const int MAX_HTF = 12 +const int MAX_LTF = 4 + +// ======================== 工具函数 ======================== +f_dash(series float v, int phase, int dashLen, int dashGap) => + // 通过丢弃部分柱来营造虚线效果 + isShow = (bar_index + phase) % (dashLen + dashGap) < dashLen + isShow ? v : na + +f_strToSize(string s) => switch s + "tiny" => size.tiny + "small" => size.small + "normal" => size.normal + "large" => size.large + => size.huge + +// 颜色调色板(循环使用) +var color[] PALETTE = array.new_color() +if barstate.isfirst and array.size(PALETTE) == 0 + array.push(PALETTE, color.new(color.teal, 0)) + array.push(PALETTE, color.new(color.orange, 0)) + array.push(PALETTE, color.new(color.fuchsia,0)) + array.push(PALETTE, color.new(color.aqua, 0)) + array.push(PALETTE, color.new(color.yellow, 0)) + array.push(PALETTE, color.new(color.purple, 0)) + array.push(PALETTE, color.new(color.lime, 0)) + array.push(PALETTE, color.new(color.red, 0)) + array.push(PALETTE, color.new(color.blue, 0)) + array.push(PALETTE, color.new(color.navy, 0)) + array.push(PALETTE, color.new(color.maroon, 0)) + array.push(PALETTE, color.new(color.silver, 0)) + +f_paletteColor(int idx) => + sz = math.max(1, array.size(PALETTE)) + array.get(PALETTE, idx % sz) + +// 是否为秒级分辨率,如 "1S","5S" +f_isSecondsTf(string tf) => + ln = str.length(tf) + ln > 0 and str.substring(tf, ln - 1, ln) == 'S' + +// 计算可视范围边界(近 N 根 K 线 + 百分比留白) +rangeHi = ta.highest(high, lookbackBars) +rangeLo = ta.lowest(low, lookbackBars) +rangeSpan = rangeHi - rangeLo +pad = rangeSpan * padPercent * 0.01 +minY = rangeLo - pad +maxY = rangeHi + pad + +// 仅在可视区间内绘制(超出返回 na,以避免影响自动缩放)。 +f_gate(series float v) => protectAutoscale ? (v >= minY and v <= maxY ? v : na) : v + +// 检查是否在可视区间内 +f_inRange(series float v) => protectAutoscale ? (v >= minY and v <= maxY) : true + +// 周期字符串美化(如 1 -> 1m, 60 -> 1h, D -> 1D, W -> 1W, 3M -> 3M) +f_prettyTf(string tf) => + s = timeframe.in_seconds(tf) + string out = tf + if not na(s) + if s < 3600 + mins = math.max(1, math.round(s / 60)) + out := str.tostring(mins) + 'm' + else if s % 3600 == 0 and s < 86400 + hrs = math.round(s / 3600) + out := str.tostring(hrs) + 'h' + else if s % 86400 == 0 and s < 7 * 86400 + days = math.round(s / 86400) + out := str.tostring(days) + 'D' + else if s % (7 * 86400) == 0 and s < 30 * 86400 + weeks = math.round(s / (7 * 86400)) + out := str.tostring(weeks) + 'W' + else + out := tf + else + // 月等返回 na 的分辨率保持原样(M/3M) + out := tf + out + +// ======================== 标准时间周期表 ======================== +// 备注:TradingView 分辨率字符串 —— 分钟: "1","3","5"...;小时: "60","120"...; +// 天: "D","2D";周: "W","2W";月: "M","3M";秒级可能为 "1S","5S" 等。 +var string[] ALL_RES = array.new_string() +if barstate.isfirst and array.size(ALL_RES) == 0 + // 由低到高,便于筛选 + // 秒级(若品种/权限不支持,请忽略,筛选时会自动排除) + array.push(ALL_RES, "1S") + array.push(ALL_RES, "5S") + array.push(ALL_RES, "15S") + array.push(ALL_RES, "30S") + // 分钟 + array.push(ALL_RES, "1") + array.push(ALL_RES, "3") + array.push(ALL_RES, "5") + array.push(ALL_RES, "15") + array.push(ALL_RES, "30") + array.push(ALL_RES, "45") + // 小时(以分钟表示) + array.push(ALL_RES, "60") + array.push(ALL_RES, "120") + array.push(ALL_RES, "240") + array.push(ALL_RES, "360") + array.push(ALL_RES, "480") + array.push(ALL_RES, "720") + // 日/周/月 + array.push(ALL_RES, "D") + array.push(ALL_RES, "2D") + array.push(ALL_RES, "3D") + array.push(ALL_RES, "W") + array.push(ALL_RES, "2W") + array.push(ALL_RES, "M") + array.push(ALL_RES, "3M") + +// ======================== 周期间关系与选择 ======================== +curResStr = timeframe.period +curResSec = timeframe.in_seconds(curResStr) + +// 构建长/次级别列表 +var string[] htfRes = array.new_string() +var string[] ltfRes = array.new_string() +array.clear(htfRes) +array.clear(ltfRes) +for i = 0 to array.size(ALL_RES) - 1 + res = array.get(ALL_RES, i) + // 非高级用户默认不包含秒级周期 + if not includeSeconds and f_isSecondsTf(res) + continue + rsec = timeframe.in_seconds(res) + // timeframe.in_seconds 不支持时返回 na,需跳过 + if na(rsec) + continue + if rsec > curResSec + array.push(htfRes, res) + else if rsec < curResSec + array.push(ltfRes, res) + +// 选取靠近当前周期的若干次级别(更有参考意义):取 ltfRes 的末尾(更接近当前) +var string[] ltfSel = array.new_string() +array.clear(ltfSel) +ltfsz = array.size(ltfRes) +if ltfsz > 0 + take = math.min(maxLTFInput, MAX_LTF, ltfsz) + for k = 0 to take - 1 + array.push(ltfSel, array.get(ltfRes, ltfsz - 1 - k)) + +// 限制长级别数量 +htfCountSelected = math.min(array.size(htfRes), maxHTFInput, MAX_HTF) +ltfCountSelected = math.min(array.size(ltfSel), MAX_LTF) + +// ======================== 计算各周期 EMA52 ======================== +// 预分配数值数组(保持固定最大长度) +var float[] htfVals = array.new_float() +var float[] ltfVals = array.new_float() +if barstate.isfirst + for _i = 0 to MAX_HTF - 1 + array.push(htfVals, na) + for _j = 0 to MAX_LTF - 1 + array.push(ltfVals, na) + +// 填充长级别数值 +for i = 0 to MAX_HTF - 1 + float val = na + if enableHTF and i < htfCountSelected + tfStr = array.get(htfRes, i) + // 计算该长级别的 EMA52 + val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(htfVals, i, val) + +// 填充次级别数值 +for i = 0 to MAX_LTF - 1 + float val = na + if enableLTF and i < ltfCountSelected + tfStr = array.get(ltfSel, i) + // 计算该次级别的 EMA52(低级别向上合成,注意其更频繁更新) + val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(ltfVals, i, val) + +// ======================== 绘制(虚线 + 标签) ======================== +// 顶层固定 plot(避免在局部/循环中调用 plot) +plot((enableHTF and 0 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 0), 0 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 1', color=f_paletteColor(0), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 1 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 1), 1 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 2', color=f_paletteColor(1), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 2 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 2), 2 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 3', color=f_paletteColor(2), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 3 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 3), 3 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 4', color=f_paletteColor(3), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 4 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 4), 4 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 5', color=f_paletteColor(4), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 5 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 5), 5 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 6', color=f_paletteColor(5), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 6 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 6), 6 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 7', color=f_paletteColor(6), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 7 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 7), 7 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 8', color=f_paletteColor(7), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 8 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 8), 8 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 9', color=f_paletteColor(8), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 9 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 9), 9 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 10', color=f_paletteColor(9), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 10 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 10), 10 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 11', color=f_paletteColor(10), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 11 < htfCountSelected) ? f_gate(f_dash(array.get(htfVals, 11), 11 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 12', color=f_paletteColor(11), linewidth=lineWidthHTF, style=plot.style_line) + +plot((enableLTF and 0 < ltfCountSelected) ? f_gate(f_dash(array.get(ltfVals, 0), 0 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 1', color=color.new(f_paletteColor(0), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 1 < ltfCountSelected) ? f_gate(f_dash(array.get(ltfVals, 1), 1 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 2', color=color.new(f_paletteColor(1), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 2 < ltfCountSelected) ? f_gate(f_dash(array.get(ltfVals, 2), 2 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 3', color=color.new(f_paletteColor(2), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 3 < ltfCountSelected) ? f_gate(f_dash(array.get(ltfVals, 3), 3 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 4', color=color.new(f_paletteColor(3), 30), linewidth=lineWidthLTF, style=plot.style_line) + +// 右侧标签(仅在最后一根柱更新,避免重复创建) +var label[] htfLabels = array.new_label() +var label[] ltfLabels = array.new_label() +var string lastTicker = na +var string lastTf = na +if barstate.isfirst + for _i = 0 to MAX_HTF - 1 + array.push(htfLabels, na) + for _j = 0 to MAX_LTF - 1 + array.push(ltfLabels, na) + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +// 更新/清理标签 +// 如果品种或周期变化,清理旧标签 +symbolChanged = lastTicker != syminfo.tickerid +tfChanged = lastTf != timeframe.period +if symbolChanged or tfChanged + for i = 0 to MAX_HTF - 1 + lab = array.get(htfLabels, i) + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + for i = 0 to MAX_LTF - 1 + lab = array.get(ltfLabels, i) + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +if barstate.islast + // 长级别 + for i = 0 to MAX_HTF - 1 + lab = array.get(htfLabels, i) + if enableHTF and i < htfCountSelected + tfStr = array.get(htfRes, i) + v = array.get(htfVals, i) + col = f_paletteColor(i) + txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) + // 可视区间外则隐藏标签;区间内按真实价格绘制,确保与标尺一致 + if protectAutoscale and not f_inRange(v) + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + else + if na(lab) + lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) + array.set(htfLabels, i, lab) + else + label.set_xy(lab, bar_index + labelOffsetBars, v) + label.set_text(lab, txt) + label.set_textcolor(lab, col) + label.set_color(lab, color.new(col, labelBgAlpha)) + label.set_style(lab, label.style_label_left) + label.set_size(lab, f_strToSize(labelSize)) + else + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + + // 次级别 + for i = 0 to MAX_LTF - 1 + lab = array.get(ltfLabels, i) + if enableLTF and i < ltfCountSelected + tfStr = array.get(ltfSel, i) + v = array.get(ltfVals, i) + base = f_paletteColor(i) + col = color.new(base, 0) + txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) + if protectAutoscale and not f_inRange(v) + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + else + if na(lab) + lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10), size=f_strToSize(labelSize)) + array.set(ltfLabels, i, lab) + else + label.set_xy(lab, bar_index + labelOffsetBars, v) + label.set_text(lab, txt) + label.set_textcolor(lab, col) + label.set_color(lab, color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10)) + label.set_style(lab, label.style_label_left) + label.set_size(lab, f_strToSize(labelSize)) + else + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + +// ======================== 备注 ======================== +// 1) 当前周期通过 timeframe.period 自动识别,并用 timeframe.in_seconds( +// ) 转换成秒以进行相对大小比较。 +// 2) 长级别/次级别集合从标准分辨率表中过滤得出,绘图数量受 MAX_* 与输入上限控制。 +// 3) 虚线效果通过在 plot 中周期性输出 na 实现,性能优于逐柱绘制 line 对象。 +// 4) 标签在最后一根柱更新,避免过量对象;颜色与曲线一致。 +// 5) 次级别(低于当前)EMA52 也计算并显示,更新频率更高,默认更淡。 + + diff --git a/自定义MACD柱子面积.pine b/自定义MACD柱子面积.pine new file mode 100644 index 0000000..612e944 --- /dev/null +++ b/自定义MACD柱子面积.pine @@ -0,0 +1,82 @@ +// 自定义MACD柱子面积计算指标 +// 作者: Claude AI +// 版本: 1.0 + +//@version=5 +indicator("自定义MACD柱子面积", shorttitle="自定义MACD面积", overlay=false) + +// MACD参数 +fast_length = input.int(12, "快线长度", minval=1) +slow_length = input.int(26, "慢线长度", minval=1) +signal_length = input.int(9, "信号线长度", minval=1) + +// 面积计算参数 +reset_on_crossover = input.bool(true, "交叉时重置面积") +use_cumulative = input.bool(false, "使用累计总面积") + +// 计算MACD +[macdLine, signalLine, histLine] = ta.macd(close, fast_length, slow_length, signal_length) + +// 初始化面积变量 +var float upArea = 0.0 +var float downArea = 0.0 +var float totalUpArea = 0.0 +var float totalDownArea = 0.0 + +// 检测柱子正负性并累加面积 +if histLine > 0 + if use_cumulative + totalUpArea := totalUpArea + histLine + upArea := totalUpArea + else + upArea := upArea + histLine + + // 当从负值转为正值时,根据设置决定是否重置下跌面积 + if histLine[1] < 0 and reset_on_crossover + downArea := 0.0 +else if histLine < 0 + if use_cumulative + totalDownArea := totalDownArea + math.abs(histLine) + downArea := totalDownArea + else + downArea := downArea + math.abs(histLine) + + // 当从正值转为负值时,根据设置决定是否重置上涨面积 + if histLine[1] > 0 and reset_on_crossover + upArea := 0.0 + +// 绘制结果 +upColor = color.new(color.green, 50) +downColor = color.new(color.red, 50) + +plot(upArea, "上涨段面积", color=upColor, style=plot.style_area) +plot(downArea, "下跌段面积", color=downColor, style=plot.style_area) +plot(histLine, "MACD柱状图", color=histLine >= 0 ? color.green : color.red, style=plot.style_histogram) +plot(macdLine, "MACD", color=color.blue) +plot(signalLine, "信号线", color=color.orange) + +// 添加文字标签 +var label upLabel = na +var label downLabel = na + +if barstate.islast + upLabel := label.new( + bar_index, upArea, + text="上涨面积: " + str.tostring(upArea, "#.##"), + color=color.green, + style=label.style_label_down, + textcolor=color.white) + + downLabel := label.new( + bar_index, downArea * -1, + text="下跌面积: " + str.tostring(downArea, "#.##"), + color=color.red, + style=label.style_label_up, + textcolor=color.white) + + label.delete(upLabel[1]) + label.delete(downLabel[1]) + +// 绘制上涨/下跌面积比率 +ratio = upArea / (downArea + 0.0001) +plot(ratio, "上涨/下跌比率", color=color.purple, linewidth=2) \ No newline at end of file diff --git a/高级MACD背驰检测.pine b/高级MACD背驰检测.pine new file mode 100644 index 0000000..e7d285b --- /dev/null +++ b/高级MACD背驰检测.pine @@ -0,0 +1,208 @@ +// 高级MACD背驰检测指标 +// 作者: Claude AI +// 版本: 1.0 + +//@version=5 +indicator("高级MACD背驰检测", shorttitle="高级MACD背驰", overlay=true) + +// === 输入参数 === + +// MACD参数 +fast_length = input.int(12, "MACD快线长度", minval=1, group="MACD设置") +slow_length = input.int(26, "MACD慢线长度", minval=1, group="MACD设置") +signal_length = input.int(9, "MACD信号线长度", minval=1, group="MACD设置") +src = input.source(close, "价格数据源", group="MACD设置") + +// 背驰检测参数 +lookback = input.int(10, "回溯检测周期", minval=5, group="背驰设置") +pivot_confirmation = input.int(3, "高低点确认柱数", minval=1, maxval=5, group="背驰设置") +regular_div = input.bool(true, "检测常规背驰", group="背驰类型") +hidden_div = input.bool(true, "检测隐形背驰", group="背驰类型") + +// 显示设置 +show_labels = input.bool(true, "显示文字标签", group="显示设置") +show_alerts = input.bool(true, "生成提醒", group="显示设置") +bull_color = input.color(color.green, "多头背驰颜色", group="显示设置") +bear_color = input.color(color.red, "空头背驰颜色", group="显示设置") + +// === 计算指标 === + +// 计算MACD +[macdLine, signalLine, histLine] = ta.macd(src, fast_length, slow_length, signal_length) + +// === 寻找峰谷 === + +// 函数:检测高点 +isPivotHigh(len) => + high[len] > high[len-1] and high[len] > high[len+1] and + high[len] > high[len-2] and high[len] > high[len+2] + +// 函数:检测低点 +isPivotLow(len) => + low[len] < low[len-1] and low[len] < low[len+1] and + low[len] < low[len-2] and low[len] < low[len+2] + +// 寻找价格和MACD的峰和谷 +priceHighs = array.new_float(0) +priceLows = array.new_float(0) +macdHighs = array.new_float(0) +macdLows = array.new_float(0) +priceHighsPos = array.new_int(0) +priceLowsPos = array.new_int(0) +macdHighsPos = array.new_int(0) +macdLowsPos = array.new_int(0) + +// 检测最近的高点和低点 +for i = pivot_confirmation to lookback + if isPivotHigh(i) + array.push(priceHighs, high[i]) + array.push(priceHighsPos, bar_index[i]) + array.push(macdHighs, macdLine[i]) + array.push(macdHighsPos, bar_index[i]) + break + +for i = pivot_confirmation to lookback + if isPivotLow(i) + array.push(priceLows, low[i]) + array.push(priceLowsPos, bar_index[i]) + array.push(macdLows, macdLine[i]) + array.push(macdLowsPos, bar_index[i]) + break + +// === 背驰检测 === + +// 常规顶背驰:价格创新高,但MACD没有创新高 +bearish_regular_divergence = false +if regular_div and array.size(priceHighs) >= 2 and array.size(macdHighs) >= 2 + price_high_current = array.get(priceHighs, 0) + price_high_prev = array.get(priceHighs, 1) + macd_high_current = array.get(macdHighs, 0) + macd_high_prev = array.get(macdHighs, 1) + + if price_high_current > price_high_prev and macd_high_current < macd_high_prev + bearish_regular_divergence := true + +// 常规底背驰:价格创新低,但MACD没有创新低 +bullish_regular_divergence = false +if regular_div and array.size(priceLows) >= 2 and array.size(macdLows) >= 2 + price_low_current = array.get(priceLows, 0) + price_low_prev = array.get(priceLows, 1) + macd_low_current = array.get(macdLows, 0) + macd_low_prev = array.get(macdLows, 1) + + if price_low_current < price_low_prev and macd_low_current > macd_low_prev + bullish_regular_divergence := true + +// 隐形顶背驰:价格未创新高,但MACD创新高(上涨趋势中的调整) +bearish_hidden_divergence = false +if hidden_div and array.size(priceHighs) >= 2 and array.size(macdHighs) >= 2 + price_high_current = array.get(priceHighs, 0) + price_high_prev = array.get(priceHighs, 1) + macd_high_current = array.get(macdHighs, 0) + macd_high_prev = array.get(macdHighs, 1) + + if price_high_current < price_high_prev and macd_high_current > macd_high_prev + bearish_hidden_divergence := true + +// 隐形底背驰:价格未创新低,但MACD创新低(下跌趋势中的反弹) +bullish_hidden_divergence = false +if hidden_div and array.size(priceLows) >= 2 and array.size(macdLows) >= 2 + price_low_current = array.get(priceLows, 0) + price_low_prev = array.get(priceLows, 1) + macd_low_current = array.get(macdLows, 0) + macd_low_prev = array.get(macdLows, 1) + + if price_low_current > price_low_prev and macd_low_current < macd_low_prev + bullish_hidden_divergence := true + +// === 绘制背驰信号 === + +// 常规顶背驰标记 +plotshape( + bearish_regular_divergence, + title="常规顶背驰", + location=location.abovebar, + color=bear_color, + style=shape.triangledown, + size=size.normal, + text="顶背驰") + +// 常规底背驰标记 +plotshape( + bullish_regular_divergence, + title="常规底背驰", + location=location.belowbar, + color=bull_color, + style=shape.triangleup, + size=size.normal, + text="底背驰") + +// 隐形顶背驰标记 +plotshape( + bearish_hidden_divergence, + title="隐形顶背驰", + location=location.abovebar, + color=color.new(bear_color, 50), + style=shape.xcross, + size=size.normal, + text="隐顶") + +// 隐形底背驰标记 +plotshape( + bullish_hidden_divergence, + title="隐形底背驰", + location=location.belowbar, + color=color.new(bull_color, 50), + style=shape.xcross, + size=size.normal, + text="隐底") + +// === 显示标签 === + +if show_labels and barstate.islast + if bearish_regular_divergence + label.new( + bar_index, high, + text="常规顶背驰\n价格创新高但MACD未跟进", + color=bear_color, + style=label.style_label_down, + textcolor=color.white) + + if bullish_regular_divergence + label.new( + bar_index, low, + text="常规底背驰\n价格创新低但MACD未跟进", + color=bull_color, + style=label.style_label_up, + textcolor=color.white) + + if bearish_hidden_divergence + label.new( + bar_index, high, + text="隐形顶背驰\n价格未创新高但MACD创新高", + color=color.new(bear_color, 50), + style=label.style_label_down, + textcolor=color.white) + + if bullish_hidden_divergence + label.new( + bar_index, low, + text="隐形底背驰\n价格未创新低但MACD创新低", + color=color.new(bull_color, 50), + style=label.style_label_up, + textcolor=color.white) + +// === 生成提醒 === + +if show_alerts + if bearish_regular_divergence + alert("MACD常规顶背驰警告!", alert.freq_once_per_bar_close) + + if bullish_regular_divergence + alert("MACD常规底背驰警告!", alert.freq_once_per_bar_close) + + if bearish_hidden_divergence + alert("MACD隐形顶背驰警告!", alert.freq_once_per_bar_close) + + if bullish_hidden_divergence + alert("MACD隐形底背驰警告!", alert.freq_once_per_bar_close) \ No newline at end of file