From 0c28e0ad97339135dc9497e22c738e4870af453b Mon Sep 17 00:00:00 2001 From: gavindiaz Date: Sat, 11 Jul 2026 20:13:58 +0000 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8C/=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 98 +++++ SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md | 531 ++++++++++++++++++++++++++++ SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md | 531 ++++++++++++++++++++++++++++ SIGNAL_EXECUTION_STANDARD.md | 182 ++++++++++ multi_indicator_composite.py | 125 +++++++ 5 files changed, 1467 insertions(+) create mode 100644 README.md create mode 100644 SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md create mode 100644 SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md create mode 100644 SIGNAL_EXECUTION_STANDARD.md create mode 100644 multi_indicator_composite.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..100802a --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# QuantDinger 策略与指标开发文档集 + +本目录包含 QuantDinger 量化交易平台的策略/指标开发相关文档与示例代码,覆盖从入门到进阶的完整开发链路。 + +--- + +## 目录结构 + +``` +QuantDingerdocs/ +├── README.md # 本文件 +├── STRATEGY_DEV_GUIDE_CN.md # 策略开发指南(中文) +├── STRATEGY_DEV_GUIDE.md # 策略开发指南(英文) +├── SIGNAL_EXECUTION_STANDARD_CN.md # 信号与执行标准(中文) +├── SIGNAL_EXECUTION_STANDARD.md # 信号与执行标准(英文) +├── SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md # 脚本代码开发指南(中文) +├── SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md # 脚本代码开发指南(英文) +├── CROSS_SECTIONAL_STRATEGY_GUIDE_CN.md # 截面策略使用指南(中文) +├── CROSS_SECTIONAL_STRATEGY_GUIDE_EN.md # 截面策略使用指南(英文) +├── EXTENSION_GUIDE.md # 扩展指南(英文) +├── STRATEGY_RUNTIME_ALGO_TRADING_IMPLEMENTATION_PLAN_CN.md # 复杂策略运行时改造方案 +├── STRATEGY_RUNTIME_ALGO_TRADING_UPGRADE_CN.md # 策略运行时与算法交易升级规划 +├── dual_ma_with_params.py # 双均线策略示例 +├── multi_indicator_composite.py # 多指标组合策略示例 +├── template_four_way_edge.py # 四路信号模板 +├── cross_sectional_momentum_rsi.py # 截面动量+RSI 示例 +└── examples/ # 示例代码副本(同上四个 py 文件) +``` + +--- + +## 文档说明 + +### 核心必读 + +| 文档 | 说明 | +|------|------| +| **[信号与执行标准](SIGNAL_EXECUTION_STANDARD_CN.md)** | 平台级契约,定义策略信号的语义、回测/实盘对齐规则、两路/四路信号选型、退出负责人模型。**所有策略开发者必读**。 | +| **[策略开发指南](STRATEGY_DEV_GUIDE_CN.md)** | 站在开发者视角,讲解 `IndicatorStrategy`(指标策略)和 `ScriptStrategy`(脚本策略)两条开发路径的区别、适用场景和编写方法。 | +| **[脚本代码开发指南](SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md)** | 深入讲解 `ScriptStrategy` 的 `on_init/on_bar` 生命周期、状态管理、分批建仓、止盈止损等复杂场景的编写规范。 | + +### 进阶专题 + +| 文档 | 说明 | +|------|------| +| **[截面策略指南](CROSS_SECTIONAL_STRATEGY_GUIDE_CN.md)** | 多标的截面策略(Cross-Sectional)的配置与使用说明,涵盖因子评分、排序、组合管理和定期调仓。 | +| **[扩展指南](EXTENSION_GUIDE.md)** | 后端扩展开发指南,覆盖 API 端点、Agent Gateway、服务层、数据适配器等模块的扩展规范。 | +| **[策略运行时改造方案](STRATEGY_RUNTIME_ALGO_TRADING_IMPLEMENTATION_PLAN_CN.md)** | 脚本策略从"信号生成"升级为"复杂状态机与算法执行"的实施方案,涉及分仓、马丁、均价止盈、重启恢复等。 | +| **[策略运行时升级规划](STRATEGY_RUNTIME_ALGO_TRADING_UPGRADE_CN.md)** | 对策略运行时与算法交易基础设施升级的规划建议,分析当前能力边界与未来演进方向。 | + +--- + +## 示例代码 + +| 文件 | 说明 | +|------|------| +| **[dual_ma_with_params.py](dual_ma_with_params.py)** | 双均线交叉策略(四路信号),演示 `# @param`、`# @strategy` 元数据声明与边缘触发信号生成。 | +| **[multi_indicator_composite.py](multi_indicator_composite.py)** | 多指标组合策略,演示均线 + RSI + MACD + 成交量过滤的组合信号写法。 | +| **[template_four_way_edge.py](template_four_way_edge.py)** | 四路信号空壳模板,复制到指标 IDE 作为新策略起点,对齐信号执行标准 v1。 | +| **[cross_sectional_momentum_rsi.py](cross_sectional_momentum_rsi.py)** | 截面策略研究参考,演示动量 + RSI 复合因子对多标的打分排序思路。 | + +--- + +## 推荐阅读顺序 + +``` +1. SIGNAL_EXECUTION_STANDARD_CN.md → 理解平台合约与信号语义 +2. STRATEGY_DEV_GUIDE_CN.md → 掌握 IndicatorStrategy / ScriptStrategy 两条路径 +3. 示例代码(.py 文件) → 对照模板上手写策略 +4. SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md → 深入脚本策略的复杂场景 +5. CROSS_SECTIONAL_STRATEGY_GUIDE_CN.md → 需要多标的同时交易时阅读 +6. 运行时相关文档 → 需要复杂状态机/算法执行时阅读 +7. EXTENSION_GUIDE.md → 需要扩展后端功能时阅读 +``` + +--- + +## 关键概念速览 + +### 两条开发路径 + +| | IndicatorStrategy | ScriptStrategy | +|------|------|------| +| 驱动方式 | 基于 `df` 批量计算指标序列 | 基于 `on_init/on_bar` 逐根事件驱动 | +| 适用场景 | 指标研究、信号原型、参数调优、信号型回测 | 有状态策略、分批建仓、动态仓位管理、实盘执行 | +| 信号输出 | 布尔型 `buy`/`sell`/`close_buy`/`close_sell` | 通过 `ctx.buy()`/`ctx.sell()`/`ctx.close_position()` 发出动作 | + +### 四路信号形态 + +``` +signal_form: four_way +├── buy → 开多信号 +├── sell → 开空信号 +├── close_buy → 平多信号(结构性反转) +└── close_sell → 平空信号(结构性反转) +``` + +> 退出负责人(exit_owner)为 `engine` 时,固定价格止盈止损由引擎管理;为 `indicator` 时,所有退出逻辑写入信号代码。 \ No newline at end of file diff --git a/SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md b/SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md new file mode 100644 index 0000000..ddb9891 --- /dev/null +++ b/SCRIPT_CODE_DEVELOPMENT_GUIDE_CN.md @@ -0,0 +1,531 @@ +# QuantDinger 脚本代码开发指南 + +本文说明如何编写可回测、可实盘、可维护的 QuantDinger 脚本代码。脚本代码适合有运行状态的策略,例如分层建仓、篮子均价止盈、追踪止损、金字塔加仓、冷却期、重复下单防护等。 + +## 1. 核心边界 + +脚本代码只负责策略逻辑。用户运行时填写的内容不应该再写进代码参数。 + +运行面板负责: + +- 标的,例如 BTC/USDT +- 市场类型,现货或合约 +- 交易方向,做多、做空或双向 +- 投入金额 +- 合约杠杆 +- 账户、通知、实盘风控开关 + +脚本代码负责: + +- 入场条件 +- 加仓、减仓、止盈、止损规则 +- 分层数量、间距、倍数、周期、冷却期 +- 状态持久化和防重复下单 +- 日志与篮子状态 checkpoint + +不要在脚本中写: + +```python +ctx.param('direction', 'long') +ctx.param('market_type', 'swap') +ctx.param('investment_amount', 1000) +ctx.param('leverage', 3) +ctx.param('base_notional', 50) +``` + +这些值应由运行面板传入,可在脚本中读取: + +```python +direction = ctx.direction +market_type = ctx.market_type +budget = ctx.investment_amount +leverage = ctx.leverage +``` + +## 2. 生命周期 + +脚本必须包含: + +```python +def on_init(ctx): + pass + +def on_bar(ctx, bar): + pass +``` + +`on_init(ctx)` 在脚本启动时执行一次,用于读取策略参数和初始化默认值。 + +`on_bar(ctx, bar)` 每根 K 线执行一次。当前统一使用 1m K 线作为脚本粒度;实盘还会每 10 秒检查一次最新价格,用于降低止盈止损响应延迟。回测只基于历史 K 线,因此极高频 tick 级逻辑不应该写在脚本里。 + +`bar` 支持: + +```python +bar['open'] +bar['high'] +bar['low'] +bar['close'] +bar['volume'] +bar['timestamp'] +``` + +## 3. 参数设计 + +只把策略结构参数放进 `ctx.param(...)`: + +```python +def on_init(ctx): + ctx.fast_period = ctx.param('fast_period', 12) + ctx.slow_period = ctx.param('slow_period', 36) + ctx.take_profit_pct = ctx.param('take_profit_pct', 0.006) + ctx.max_layers = ctx.param('max_layers', 5) +``` + +百分比默认值在 Python 里使用 0-1 小数: + +- `0.006` 表示 0.6% +- `0.02` 表示 2% +- `0.8` 表示 80% + +前端可以用 0-100 的显示方式,但写回代码时应保持 Python 小数。 + +## 4. 投入金额和下单金额 + +推荐所有状态型脚本使用 `ctx.basket(side).open_child_order(..., notional=quote_amount)`。 + +`notional` 表示计价货币金额,例如 USDT 金额。回测和实盘会根据市场类型、价格和杠杆换算为实际下单数量。 + +示例: + +```python +def _run_budget(ctx): + try: + budget = float(ctx.investment_amount or 0.0) + except Exception: + budget = 0.0 + if budget > 0: + return budget + return 0.0 + +def _planned_base_notional(ctx): + total_weight = 1 + 1.8 + 3.24 + return _run_budget(ctx) / total_weight + +def on_bar(ctx, bar): + price = float(bar['close']) + side = 'short' if str(ctx.direction).lower() == 'short' else 'long' + basket = ctx.basket(side) + quote_amount = _planned_base_notional(ctx) + basket.open_child_order( + layer=1, + order=1, + notional=quote_amount, + price=price, + action='open', + payload={'reason': 'first_entry'}, + ) +``` + +不要把“基础下单金额”作为模板参数暴露给用户。更清晰的模型是:用户填投入金额,脚本根据层数、倍数和总权重自动拆分每个子单的计划金额。 + +## 5. 方向处理 + +方向由运行面板选择。脚本只读取,不写死。 + +推荐写法: + +```python +def _side(ctx): + try: + direction = str(ctx.direction) + except Exception: + direction = 'long' + return 'short' if direction.lower() == 'short' else 'long' +``` + +现货只能做多;这个限制由运行面板和执行层处理。脚本里不需要再做一套复杂分支。 + +## 6. 状态管理 + +有分层、篮子、冷却期的策略必须使用 `ctx.state`。 + +常见状态: + +- 当前层数:`layer` +- 当前子单:`order` +- 均价:`avg_cost` +- 总数量:`qty` +- 下一次触发价格:`next_trigger` +- 冷却到哪根 K 线:`cooldown_until` +- 上次下单 K 线:`last_order_bar` + +示例: + +```python +bar_no = int(ctx.current_index) +last_order_bar = int(ctx.state.get('last_order_bar', -999999) or -999999) +if last_order_bar == bar_no: + return + +ctx.state.set('last_order_bar', bar_no) +``` + +这样可以防止同一根 K 线重复发单。 + +## 7. Basket API + +`ctx.basket(side)` 适合所有有篮子概念的策略。 + +常用方法: + +```python +basket = ctx.basket('long') + +basket.open_child_order( + layer=1, + order=1, + notional=50, + price=price, + action='open', + payload={'reason': 'entry'}, +) + +basket.open_child_order( + layer=2, + order=1, + notional=80, + price=price, + action='add', + payload={'reason': 'add_layer'}, +) + +basket.close_all(reason='take_profit') +``` + +`checkpoint` 用于展示策略当前状态: + +```python +basket.checkpoint( + status='opening', + current_layer=layer, + current_order_in_layer=order, + total_qty=qty, + total_notional=qty * avg_cost, + avg_entry_price=avg_cost, + next_entry_trigger=next_trigger, + take_profit_price=take_profit, + max_layer=max_layers, + max_orders_per_layer=orders_per_layer, +) +``` + +## 8. 回测和实盘对齐 + +脚本回测和实盘使用同一套 `on_init/on_bar` 语义。需要注意: + +- 回测基于历史 K 线,不模拟每 10 秒 tick 细节。 +- 实盘会额外拉取最新价格用于更及时地检查价格条件。 +- 下单金额建议使用 basket `notional`,这样合约杠杆在回测和实盘中口径一致。 +- 策略不能依赖未来数据,只能使用 `ctx.bars(n)` 获取当前及历史 K 线。 +- 每次加仓必须有价格间距、最大层数和防重复下单。 + +## 9. 沙箱限制 + +脚本在安全沙箱中执行。不要使用: + +- `getattr`、`setattr`、`delattr` +- `eval`、`exec`、`open`、`compile` +- `globals`、`vars`、`dir` +- `__builtins__`、dunder 属性 +- 文件、网络、数据库、进程、线程相关 API +- `os`、`sys`、`requests`、`urllib`、`socket`、`subprocess`、`threading`、`multiprocessing`、`sqlite3`、`psycopg`、`sqlalchemy`、`pathlib`、`tempfile`、`glob`、`io`、`operator`、`pickle`、`ctypes` + +读取可选字段时,用 `try/except` 直接访问: + +```python +try: + direction = str(ctx.direction) +except Exception: + direction = 'long' +``` + +不要写: + +```python +direction = getattr(ctx, 'direction', 'long') +``` + +## 10. 推荐模板类型 + +经典可落地的脚本模板包括: + +- EMA ATR 趋势风控:趋势跟随、ATR 止损、ATR 追踪止损。 +- Donchian 突破金字塔:通道突破入场,盈利后顺势加仓。 +- 布林均值回归篮子:触及布林外轨后分层建仓,均价回归止盈。 +- 阶梯分仓马丁篮子:多层分仓、子单马丁、均价止盈、硬止损。 + +这些模板覆盖趋势、突破、震荡均值回归和高风险篮子模型,避免把多个相似的“加仓模板”重复展示给用户。 + +## 11. 复杂策略的推荐架构 + +复杂脚本不要写成一大坨 `if/else`。推荐拆成五层: + +1. 信号层:判断是否允许入场、是否允许加仓、是否应该退出。 +2. 资金层:把运行面板的投入金额拆成每一层、每一单的计划金额。 +3. 状态层:保存当前阶段、层数、子单、均价、触发价、冷却期。 +4. 执行层:只负责发出 `basket.open_child_order` 或 `basket.close_all`。 +5. 风控层:硬止损、最大层数、最大订单数、冷却期、同 K 线防重复。 + +推荐文件结构虽然仍然是一个脚本,但代码内部应保持这种顺序: + +```python +def on_init(ctx): + # 1. 读取策略参数 + pass + +def _side(ctx): + # 2. 读取运行面板方向 + pass + +def _run_budget(ctx): + # 3. 读取运行面板投入金额 + pass + +def _planned_notional(ctx, layer, order): + # 4. 计算每一单计划金额 + pass + +def _entry_signal(ctx, bar, bars): + # 5. 入场信号 + pass + +def _risk_exit(ctx, price, avg_cost): + # 6. 风控退出 + pass + +def _place_child(ctx, basket, layer, order, price, action): + # 7. 发单并更新状态 + pass + +def on_bar(ctx, bar): + # 8. 主流程,只编排,不塞复杂公式 + pass +``` + +这样用户后续加新条件、换资金模型、换退出逻辑时,不需要重写整份策略。 + +## 12. 状态机模型 + +复杂策略应该先设计状态机。推荐状态: + +| 状态 | 含义 | 允许动作 | +| --- | --- | --- | +| `idle` | 空仓等待 | 判断入场 | +| `opening` | 已开首单或正在建仓 | 加仓、止盈、止损 | +| `active` | 仓位完整运行中 | 止盈、止损、追踪退出 | +| `closing` | 已触发平仓 | 等待执行层完成 | +| `cooldown` | 平仓后冷却 | 不允许重新入场 | + +脚本里可以不用显式保存字符串状态,但逻辑上必须有这套概念。最少要保存: + +```python +ctx.state.set('layer', layer) +ctx.state.set('order', order) +ctx.state.set('avg_cost', avg_cost) +ctx.state.set('qty', qty) +ctx.state.set('next_trigger', next_trigger) +ctx.state.set('cooldown_until', cooldown_until) +ctx.state.set('last_order_bar', bar_no) +``` + +主流程建议固定为: + +```python +def on_bar(ctx, bar): + # 1. 数据不足直接返回 + # 2. 读取运行面板方向、价格、状态 + # 3. 如果空仓,检查是否需要重置状态 + # 4. 如果冷却中,直接返回 + # 5. 如果空仓,判断入场 + # 6. 如果持仓,先判断止盈/止损 + # 7. 如果仍持仓,再判断加仓 + # 8. 发单后更新 checkpoint 和 state +``` + +不要先加仓再判断止损。复杂策略里这个顺序很重要。 + +## 13. 资金拆分模型 + +用户只填一个“投入金额”。脚本需要根据策略结构拆分每个子单。 + +### 13.1 等额分层 + +适合突破金字塔: + +```python +base_notional = ctx.investment_amount / ctx.max_layers +``` + +如果投入 1000 USDT、4 层,则每层 250 USDT。 + +### 13.2 温和递增分层 + +适合布林均值回归: + +```python +weights = [ctx.layer_multiplier ** i for i in range(ctx.max_layers)] +base_notional = ctx.investment_amount / sum(weights) +layer_notional = base_notional * (ctx.layer_multiplier ** (layer - 1)) +``` + +如果投入 1000 USDT、4 层、倍数 1.25,则每层按权重 1、1.25、1.56、1.95 拆分。 + +### 13.3 分仓马丁 + +适合 5 个分仓、每仓 3 单: + +```python +one_layer_weight = sum([ctx.martingale_multiplier ** i for i in range(ctx.orders_per_layer)]) +total_weight = one_layer_weight * ctx.max_layers +base_notional = ctx.investment_amount / total_weight +child_notional = base_notional * (ctx.martingale_multiplier ** (order - 1)) +``` + +如果投入金额是 1000 USDT、5 层、每层 3 单、倍数 1.8: + +- 单层权重:`1 + 1.8 + 3.24 = 6.04` +- 总权重:`6.04 * 5 = 30.2` +- 基础子单金额:`1000 / 30.2 = 33.11 USDT` +- 每层三单约为:`33.11 / 59.60 / 107.27 USDT` + +这能避免“代码里写了基础下单金额,用户又填投入金额”的冲突。 + +## 14. 信号层设计 + +复杂策略的入场信号最好是“事件”,不是“状态”。 + +不推荐: + +```python +if not has_position and fast > slow: + open_position() +``` + +这样会在趋势状态持续时频繁尝试入场,必须依赖其他防线兜底。 + +推荐: + +```python +cross_up = prev_fast <= prev_slow and fast > slow +if not has_position and cross_up: + open_position() +``` + +常见信号模型: + +- 趋势:EMA 金叉/死叉、价格突破均线并确认。 +- 突破:当前收盘价突破过去 N 根最高/最低。 +- 均值回归:价格触及布林带外轨,并用 RSI 过滤极端状态。 +- 分仓马丁:首单可以无信号启动,也可以加趋势/震荡过滤;后续加仓只能由价格间距触发。 + +## 15. 风控层设计 + +复杂策略必须至少有三类风控: + +1. 结构风控:最大层数、每层最大子单数、单 K 线只允许一次下单。 +2. 价格风控:硬止损、均价止盈、追踪止损、通道退出。 +3. 时间风控:冷却期、超时退出、长时间未成交后的状态重置。 + +示例: + +```python +if last_order_bar == bar_no: + return + +if layer >= ctx.max_layers and order >= ctx.orders_per_layer: + # 不再继续加仓,只等待止盈或硬止损 + return + +if pnl <= -ctx.hard_stop_pct: + basket.close_all(reason='hard_stop') + _reset_cycle(ctx, bar_no + ctx.cooldown_bars) + return +``` + +## 16. 回测前检查清单 + +用户写完复杂脚本后,先检查: + +- 是否定义了 `on_init(ctx)` 和 `on_bar(ctx, bar)`。 +- 是否没有使用 `getattr`、文件、网络、数据库、进程等沙箱禁止能力。 +- 是否没有把方向、市场类型、投入金额、杠杆写成 `ctx.param`。 +- 是否所有百分比参数在代码里都是 0-1 小数。 +- 是否所有加仓都有最大次数和价格间距。 +- 是否所有发单都有同 K 线防重复。 +- 是否下单金额来自 `ctx.investment_amount` 的拆分。 +- 是否使用 `basket.open_child_order(..., notional=...)` 表示计价金额。 +- 是否在止盈/止损后重置状态并进入冷却。 +- 是否能解释每一个状态变量的含义。 + +## 17. 实盘前检查清单 + +回测通过不等于可以直接实盘。实盘前还要检查: + +- 现货策略是否只做多。 +- 合约策略的杠杆是否符合交易所限制。 +- 投入金额是否足够覆盖最大计划层数。 +- 最大单数是否会超过交易所限频或最小下单金额限制。 +- 硬止损是否存在,且不是过大到没有意义。 +- 是否设置了账户级风控、通知和异常停机策略。 +- 是否用小金额或模拟盘跑过至少一个完整开仓、加仓、平仓周期。 + +## 18. AI 助手提示词建议 + +让 AI 生成复杂脚本时,提示词要明确边界。推荐这样写: + +```text +写一个 QuantDinger 脚本代码: +1. 使用 on_init/on_bar。 +2. 运行面板负责标的、现货/合约、方向、投入金额和杠杆,代码里不要写成 ctx.param。 +3. 使用 ctx.basket(side).open_child_order(..., notional=...) 下单。 +4. 投入金额按 5 层、每层 3 单、马丁倍数 1.8 自动拆分。 +5. 每个子单之间有价格间距,均价止盈,硬止损,同 K 线防重复。 +6. 不要使用 getattr、文件、网络、数据库、导入危险模块。 +``` + +如果要改现有模板,建议说: + +```text +基于当前模板,只调整参数和风控,不要改变运行面板边界。 +把止盈改为 0.8%,层间距改成逐层扩大,最大亏损 12% 硬止损。 +``` + +## 19. 最小骨架 + +```python +""" +My Script Strategy +""" + +def on_init(ctx): + ctx.lookback = ctx.param('lookback', 20) + ctx.take_profit_pct = ctx.param('take_profit_pct', 0.01) + +def _side(ctx): + try: + direction = str(ctx.direction) + except Exception: + direction = 'long' + return 'short' if direction.lower() == 'short' else 'long' + +def on_bar(ctx, bar): + bars = ctx.bars(ctx.lookback + 1) + if len(bars) < ctx.lookback + 1: + return + + side = _side(ctx) + price = float(bar['close']) + basket = ctx.basket(side) + + # Add entry, state, risk, and exit logic here. +``` diff --git a/SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md b/SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md new file mode 100644 index 0000000..fa3b1e6 --- /dev/null +++ b/SCRIPT_CODE_DEVELOPMENT_GUIDE_EN.md @@ -0,0 +1,531 @@ +# QuantDinger Script Code Development Guide + +This guide explains how to write QuantDinger script code that can be backtested, deployed live, and maintained safely. Script code is for stateful strategies such as layered entries, basket average-cost take profit, trailing stops, pyramiding, cooldowns, and duplicate-order protection. + +## 1. Core Boundary + +Script code owns strategy logic only. Values chosen by the user at run time should not be duplicated as code parameters. + +The run panel owns: + +- Symbol, such as BTC/USDT +- Market type, spot or swap +- Trade direction, long, short, or both +- Investment amount +- Contract leverage +- Account, notifications, and live risk controls + +Script code owns: + +- Entry conditions +- Add, reduce, take-profit, and stop-loss rules +- Layer count, spacing, multipliers, periods, and cooldowns +- Persistent state and duplicate-order protection +- Logs and basket checkpoints + +Do not write these as script parameters: + +```python +ctx.param('direction', 'long') +ctx.param('market_type', 'swap') +ctx.param('investment_amount', 1000) +ctx.param('leverage', 3) +ctx.param('base_notional', 50) +``` + +Read run-panel values directly when needed: + +```python +direction = ctx.direction +market_type = ctx.market_type +budget = ctx.investment_amount +leverage = ctx.leverage +``` + +## 2. Lifecycle + +Every script must define: + +```python +def on_init(ctx): + pass + +def on_bar(ctx, bar): + pass +``` + +`on_init(ctx)` runs once when the script starts. Use it to read strategy parameters and initialize defaults. + +`on_bar(ctx, bar)` runs once per K-line bar. The platform uses a fixed 1m bar stream for script strategies; live execution also checks the latest price every 10 seconds to reduce take-profit and stop-loss latency. Backtests are K-line based, so do not write tick-level strategies that require every market tick. + +`bar` supports: + +```python +bar['open'] +bar['high'] +bar['low'] +bar['close'] +bar['volume'] +bar['timestamp'] +``` + +## 3. Parameter Design + +Only put strategy knobs into `ctx.param(...)`: + +```python +def on_init(ctx): + ctx.fast_period = ctx.param('fast_period', 12) + ctx.slow_period = ctx.param('slow_period', 36) + ctx.take_profit_pct = ctx.param('take_profit_pct', 0.006) + ctx.max_layers = ctx.param('max_layers', 5) +``` + +Percent defaults in Python code use 0-1 ratios: + +- `0.006` means 0.6% +- `0.02` means 2% +- `0.8` means 80% + +The frontend may display 0-100 percent values, but the generated Python default literal should remain a ratio. + +## 4. Investment Amount and Order Sizing + +For stateful scripts, prefer `ctx.basket(side).open_child_order(..., notional=quote_amount)`. + +`notional` means quote-currency amount, for example USDT. Backtest and live execution convert it into base quantity using market type, price, and leverage. + +Example: + +```python +def _run_budget(ctx): + try: + budget = float(ctx.investment_amount or 0.0) + except Exception: + budget = 0.0 + if budget > 0: + return budget + return 0.0 + +def _planned_base_notional(ctx): + total_weight = 1 + 1.8 + 3.24 + return _run_budget(ctx) / total_weight + +def on_bar(ctx, bar): + price = float(bar['close']) + side = 'short' if str(ctx.direction).lower() == 'short' else 'long' + basket = ctx.basket(side) + quote_amount = _planned_base_notional(ctx) + basket.open_child_order( + layer=1, + order=1, + notional=quote_amount, + price=price, + action='open', + payload={'reason': 'first_entry'}, + ) +``` + +Do not expose "base order amount" as a template parameter. A clearer model is: the user enters one investment amount, and the script derives child order amounts from layer count, multipliers, and total weights. + +## 5. Direction Handling + +Direction is selected in the run panel. The script reads it, but should not hard-code it. + +Recommended helper: + +```python +def _side(ctx): + try: + direction = str(ctx.direction) + except Exception: + direction = 'long' + return 'short' if direction.lower() == 'short' else 'long' +``` + +Spot can only run long. That constraint is enforced by the run panel and execution layer; scripts do not need a second complicated branch. + +## 6. State Management + +Layered, basket, or cooldown-based strategies must use `ctx.state`. + +Common state keys: + +- Current layer: `layer` +- Current child order: `order` +- Average cost: `avg_cost` +- Total quantity: `qty` +- Next trigger price: `next_trigger` +- Cooldown-until bar: `cooldown_until` +- Last order bar: `last_order_bar` + +Example: + +```python +bar_no = int(ctx.current_index) +last_order_bar = int(ctx.state.get('last_order_bar', -999999) or -999999) +if last_order_bar == bar_no: + return + +ctx.state.set('last_order_bar', bar_no) +``` + +This prevents duplicate orders on the same bar. + +## 7. Basket API + +`ctx.basket(side)` is the preferred API for strategies with basket semantics. + +Common usage: + +```python +basket = ctx.basket('long') + +basket.open_child_order( + layer=1, + order=1, + notional=50, + price=price, + action='open', + payload={'reason': 'entry'}, +) + +basket.open_child_order( + layer=2, + order=1, + notional=80, + price=price, + action='add', + payload={'reason': 'add_layer'}, +) + +basket.close_all(reason='take_profit') +``` + +Use `checkpoint` to expose the current strategy state: + +```python +basket.checkpoint( + status='opening', + current_layer=layer, + current_order_in_layer=order, + total_qty=qty, + total_notional=qty * avg_cost, + avg_entry_price=avg_cost, + next_entry_trigger=next_trigger, + take_profit_price=take_profit, + max_layer=max_layers, + max_orders_per_layer=orders_per_layer, +) +``` + +## 8. Backtest and Live Alignment + +Script backtest and live execution share the same `on_init/on_bar` semantics. Keep these points in mind: + +- Backtests are based on historical K-line bars and do not simulate every 10-second live price check. +- Live execution additionally checks latest price for faster price-condition handling. +- Use basket `notional` sizing so leverage behavior stays consistent across backtest and live. +- Do not use future data. Use `ctx.bars(n)` for current and historical bars only. +- Every scale-in needs a price distance, max layer/order limit, and duplicate-order guard. + +## 9. Sandbox Restrictions + +Scripts run inside a safety sandbox. Do not use: + +- `getattr`, `setattr`, `delattr` +- `eval`, `exec`, `open`, `compile` +- `globals`, `vars`, `dir` +- `__builtins__` or dunder attributes +- File, network, database, process, or thread APIs +- `os`, `sys`, `requests`, `urllib`, `socket`, `subprocess`, `threading`, `multiprocessing`, `sqlite3`, `psycopg`, `sqlalchemy`, `pathlib`, `tempfile`, `glob`, `io`, `operator`, `pickle`, or `ctypes` + +For optional fields, use direct access with `try/except`: + +```python +try: + direction = str(ctx.direction) +except Exception: + direction = 'long' +``` + +Do not write: + +```python +direction = getattr(ctx, 'direction', 'long') +``` + +## 10. Recommended Template Types + +Professional script templates should cover different market regimes instead of repeating the same scale-in idea: + +- EMA ATR trend risk: trend following with ATR hard stop and ATR trailing stop. +- Donchian breakout pyramid: channel breakout entry and favorable pyramiding. +- Bollinger mean reversion basket: layered entries around volatility-band extremes and average-cost reversion exit. +- Layered basket martingale: multiple layers, martingale child sizing, average-cost take profit, and hard stop. + +These four cover trend, breakout, range reversion, and high-risk basket models without showing several duplicate "add-on-dip" templates. + +## 11. Recommended Architecture for Complex Scripts + +Do not write complex scripts as one large `if/else` block. Split the logic into five layers: + +1. Signal layer: decide whether entry, add, or exit is allowed. +2. Sizing layer: split the run-panel investment amount into planned child order amounts. +3. State layer: persist phase, layer, child order, average cost, trigger price, and cooldown. +4. Execution layer: emit only `basket.open_child_order` or `basket.close_all`. +5. Risk layer: hard stop, max layers, max child orders, cooldown, and duplicate-order guards. + +The script is still a single file, but the internal structure should look like this: + +```python +def on_init(ctx): + # 1. Read strategy knobs + pass + +def _side(ctx): + # 2. Read run-panel direction + pass + +def _run_budget(ctx): + # 3. Read run-panel investment amount + pass + +def _planned_notional(ctx, layer, order): + # 4. Calculate planned quote amount for each child order + pass + +def _entry_signal(ctx, bar, bars): + # 5. Entry signal + pass + +def _risk_exit(ctx, price, avg_cost): + # 6. Risk exit + pass + +def _place_child(ctx, basket, layer, order, price, action): + # 7. Place order and update state + pass + +def on_bar(ctx, bar): + # 8. Main orchestration only + pass +``` + +This makes it much easier to add conditions, change sizing, or replace exit logic without rewriting the whole strategy. + +## 12. State Machine Model + +Complex strategies should start with a state machine design. Recommended phases: + +| State | Meaning | Allowed actions | +| --- | --- | --- | +| `idle` | Flat and waiting | Check entry | +| `opening` | First order placed or basket building | Add, take profit, stop | +| `active` | Position is running | Take profit, stop, trailing exit | +| `closing` | Close triggered | Wait for execution layer | +| `cooldown` | Post-exit cooldown | No re-entry | + +The script does not have to store these exact strings, but the logic should follow this model. At minimum persist: + +```python +ctx.state.set('layer', layer) +ctx.state.set('order', order) +ctx.state.set('avg_cost', avg_cost) +ctx.state.set('qty', qty) +ctx.state.set('next_trigger', next_trigger) +ctx.state.set('cooldown_until', cooldown_until) +ctx.state.set('last_order_bar', bar_no) +``` + +Recommended `on_bar` flow: + +```python +def on_bar(ctx, bar): + # 1. Return if not enough data + # 2. Read direction, price, and state + # 3. If flat, repair/reset stale state + # 4. If in cooldown, return + # 5. If flat, check entry + # 6. If in position, check take-profit/stop first + # 7. If still in position, check add logic + # 8. After any order, update checkpoint and state +``` + +Do not add first and check stop later. In complex scripts, this order matters. + +## 13. Sizing Models + +Users enter one investment amount. The script should split it according to the strategy structure. + +### 13.1 Equal Layers + +Useful for breakout pyramids: + +```python +base_notional = ctx.investment_amount / ctx.max_layers +``` + +If investment is 1000 USDT and there are 4 layers, each layer is 250 USDT. + +### 13.2 Mild Geometric Layers + +Useful for Bollinger mean reversion: + +```python +weights = [ctx.layer_multiplier ** i for i in range(ctx.max_layers)] +base_notional = ctx.investment_amount / sum(weights) +layer_notional = base_notional * (ctx.layer_multiplier ** (layer - 1)) +``` + +If investment is 1000 USDT, 4 layers, multiplier 1.25, weights are 1, 1.25, 1.56, and 1.95. + +### 13.3 Layered Martingale + +Useful for 5 layers and 3 child orders per layer: + +```python +one_layer_weight = sum([ctx.martingale_multiplier ** i for i in range(ctx.orders_per_layer)]) +total_weight = one_layer_weight * ctx.max_layers +base_notional = ctx.investment_amount / total_weight +child_notional = base_notional * (ctx.martingale_multiplier ** (order - 1)) +``` + +If investment is 1000 USDT, 5 layers, 3 orders per layer, multiplier 1.8: + +- One layer weight: `1 + 1.8 + 3.24 = 6.04` +- Total weight: `6.04 * 5 = 30.2` +- Base child order amount: `1000 / 30.2 = 33.11 USDT` +- Each layer has roughly `33.11 / 59.60 / 107.27 USDT` + +This removes the conflict between "base order amount in code" and "investment amount in the run panel". + +## 14. Signal Layer Design + +Complex entries should usually be events, not persistent states. + +Not recommended: + +```python +if not has_position and fast > slow: + open_position() +``` + +That condition remains true for many bars and relies on other guards to avoid repeated entry attempts. + +Recommended: + +```python +cross_up = prev_fast <= prev_slow and fast > slow +if not has_position and cross_up: + open_position() +``` + +Common signal models: + +- Trend: EMA cross, price crossing and confirming above/below a moving average. +- Breakout: close breaks the highest high or lowest low of the past N bars. +- Mean reversion: price touches an outer Bollinger band with RSI confirmation. +- Layered martingale: first order may start immediately or use a filter; later orders must be triggered by price distance only. + +## 15. Risk Layer Design + +Every complex strategy needs at least three types of risk control: + +1. Structure risk: max layers, max child orders, one order per bar. +2. Price risk: hard stop, average-cost take profit, trailing stop, channel exit. +3. Time risk: cooldown, timeout exit, stale-state reset. + +Example: + +```python +if last_order_bar == bar_no: + return + +if layer >= ctx.max_layers and order >= ctx.orders_per_layer: + # No more adds. Wait for take-profit or hard stop. + return + +if pnl <= -ctx.hard_stop_pct: + basket.close_all(reason='hard_stop') + _reset_cycle(ctx, bar_no + ctx.cooldown_bars) + return +``` + +## 16. Pre-Backtest Checklist + +Before running a complex script backtest, check: + +- `on_init(ctx)` and `on_bar(ctx, bar)` are defined. +- No `getattr`, file, network, database, process, or other sandbox-blocked capability is used. +- Direction, market type, investment amount, and leverage are not defined as `ctx.param`. +- Percent parameters in Python are 0-1 ratios. +- Every add has max count and price distance. +- Every order has a same-bar duplicate guard. +- Order sizing is derived from `ctx.investment_amount`. +- Orders use `basket.open_child_order(..., notional=...)` for quote amount sizing. +- State is reset after take-profit/stop and cooldown is set. +- Every state variable can be explained. + +## 17. Pre-Live Checklist + +A successful backtest does not mean the script is ready for live trading. Also check: + +- Spot scripts only run long. +- Contract leverage fits exchange limits. +- Investment amount can cover the maximum planned layers. +- Max order count does not violate exchange rate limits or minimum order size. +- Hard stop exists and is not so wide that it is meaningless. +- Account-level risk controls, notifications, and emergency stop are configured. +- The script has completed at least one full open/add/close cycle with small capital or demo trading. + +## 18. AI Assistant Prompt Examples + +When asking AI to generate complex script code, state the boundary explicitly: + +```text +Write a QuantDinger script code: +1. Use on_init/on_bar. +2. The run panel owns symbol, spot/swap, direction, investment amount, and leverage. Do not create ctx.param for these. +3. Use ctx.basket(side).open_child_order(..., notional=...) for orders. +4. Split the investment amount into 5 layers, 3 child orders per layer, martingale multiplier 1.8. +5. Use child-order spacing, average-cost take profit, hard stop, and same-bar duplicate protection. +6. Do not use getattr, file/network/database APIs, or unsafe imports. +``` + +When modifying an existing template: + +```text +Based on the current template, only adjust parameters and risk controls. Do not change the run-panel boundary. +Change take-profit to 0.8%, make inter-layer spacing grow with depth, and add a 12% hard stop. +``` + +## 19. Minimal Skeleton + +```python +""" +My Script Strategy +""" + +def on_init(ctx): + ctx.lookback = ctx.param('lookback', 20) + ctx.take_profit_pct = ctx.param('take_profit_pct', 0.01) + +def _side(ctx): + try: + direction = str(ctx.direction) + except Exception: + direction = 'long' + return 'short' if direction.lower() == 'short' else 'long' + +def on_bar(ctx, bar): + bars = ctx.bars(ctx.lookback + 1) + if len(bars) < ctx.lookback + 1: + return + + side = _side(ctx) + price = float(bar['close']) + basket = ctx.basket(side) + + # Add entry, state, risk, and exit logic here. +``` diff --git a/SIGNAL_EXECUTION_STANDARD.md b/SIGNAL_EXECUTION_STANDARD.md new file mode 100644 index 0000000..c7e0107 --- /dev/null +++ b/SIGNAL_EXECUTION_STANDARD.md @@ -0,0 +1,182 @@ +# QuantDinger Signal & Execution Standard (SSOT) + +**Version**: 1.0 +**Status**: Current +**Scope**: All `IndicatorStrategy` flows (indicator Python saved as strategies) for backtest and live +**Implementation**: `BacktestService`, `TradingExecutor`, `validate_code_safety` / `verifyCode` +**Guide**: [STRATEGY_DEV_GUIDE.md](./STRATEGY_DEV_GUIDE.md) + +--- + +## 1. Purpose + +The platform runs **many different strategies**. Without a single contract, teams see: + +- Different meanings of `buy`/`sell` across scripts; +- Backtests filling on **closed bar → next open** while live trades on **forming bars + immediate exits**; +- Indicator exits plus `# @strategy trailingEnabled` → duplicate closes and `invalid amount (0.0)` rejects. + +This document is the **single source of truth** for what authors write and how engines interpret it. +New strategies **SHOULD** comply; existing ones **SHOULD** migrate per §9. + +--- + +## 2. Terms + +| Term | Meaning | +|------|---------| +| **Signal bar** | Bar where a boolean flag becomes true (timestamp = that bar’s close) | +| **Fill bar** | Bar where execution is anchored (default: bar after signal bar) | +| **Edge trigger** | true only on false→true transition vs previous bar | +| **Repaint** | Conditions on the forming bar flip as price updates | +| **Exit owner** | One primary close path: indicator signals **or** engine risk, not both narrow rules | + +--- + +## 3. Choose a strategy form first + +| Form | Use when | Avoid when | +|------|----------|------------| +| **A. Two-way** `buy` / `sell` | Simple crossovers; symmetric reversal | Separate tp/sl per side, state machines, “close only” semantics | +| **B. Four-way** `open_long` / `close_long` / `open_short` / `close_short` | State machines, touch exits, explicit queue alignment | — (recommended default for new strategies) | +| **C. ScriptStrategy** `on_bar` + `ctx` | Position-aware bots, scaling, cooldowns | Pure vectorized signals on full `df` | + +**Platform default for new listings**: **Form B (four-way)**. + +--- + +## 4. Signal output rules (IndicatorStrategy) + +### 4.1 Universal MUST + +1. **MUST** start mutations with `df = df.copy()`. +2. **MUST** define `output` (`name`, `plots`; optional `signals` for markers only). +3. **MUST** execution columns: length `len(df)`, dtype bool after `fillna(False).astype(bool)`. +4. **MUST** edge-trigger execution columns (§4.3) unless documented otherwise. +5. **MUST NOT** use `shift(-1)` or any look-ahead. +6. **MUST NOT** mix form A and form B execution columns as fill drivers (may keep `buy`/`sell` all-false for legacy UI). + +### 4.2 Form A: `buy` / `sell` + +| `tradeDirection` | `buy=True` | `sell=True` | +|------------------|------------|-------------| +| `long` | open long | close long | +| `short` | close short | open short | +| `both` | open long; **close short first if short** | open short; **close long first if long** | + +**MUST NOT** treat `buy` as “close short only” under `both`. Use form B `close_*` for flat-only exits. + +### 4.3 Form B: Four-way (recommended) + +| Column | Meaning | +|--------|---------| +| `open_long` | Open / add long | +| `close_long` | Close long | +| `open_short` | Open / add short | +| `close_short` | Close short | + +**MUST**: all four columns exist as bool. +**SHOULD**: same-bar priority close before open; avoid simultaneous `open_long` and `open_short` on one bar. + +Fills follow **explicit four-way** semantics (not buy/sell both-mode remapping). + +### 4.4 `tradeDirection` + +`long` / `short` / `both` filters which legs are active; it does not replace four-way columns. + +--- + +## 5. Exit ownership + +Each strategy **MUST** declare one owner in comments or docs: + +| Mode | Script | `# @strategy` | +|------|--------|---------------| +| **Indicator exits** `exit_owner: indicator` | `close_*` or equivalent explicit signal exits | `trailingEnabled false`; do not rely on SL/TP/trailing | +| **Engine exits** `exit_owner: engine` | entries only, or structural reverse `close_*` signals | SL/TP/trailing as needed | + +**MUST NOT** combine tight indicator tp/sl with tight `trailingEnabled` on the same leg. + +The current backend supports only `exit_owner: indicator` and `exit_owner: engine`. Do not emit `exit_owner: layered`; a mixed “indicator exits + engine backup” mode needs an explicit product/runtime change first. + +--- + +## 6. Execution contract (backtest ↔ live) + +### 6.1 Signal timing + +| Setting | Standard | +|---------|----------| +| `signal_mode` | **`confirmed`** | +| `exit_signal_mode` | **`confirmed`** | + +Forming-bar evaluation is for display/research only by default. + +### 6.2 Fill timing + +Default: **`next_bar_open`** (signal on bar *t*, fill on bar *t+1* open ± slippage). + +### 6.3 Same-bar ordering + +Priority: `close_*` > `reduce_*` > `open_*` > `add_*`. +Document flip mode **R1** (close on bar *t*, open on *t+1*, recommended) or **R2** (same-bar flip). + +### 6.4 Close sizing + +Sync positions → resolve size from DB and exchange → retry once if zero → fail with log if still zero. + +--- + +## 7. Configuration + +Use `# @strategy` for defaults; leverage and credentials stay in product UI. +Optional header block: + +```python +# --- QuantDinger execution contract (v1) --- +# signal_form: four_way +# exit_owner: indicator +# flip_mode: R1 +# @strategy tradeDirection both +# @strategy trailingEnabled false +``` + +--- + +## 8. Release checklist + +- Form A/B/C chosen and documented +- Exit owner declared +- `confirmed` modes for live +- Backtest fills reviewed (signal bar vs fill bar) +- Pilot live run without systematic zero-amount closes + +--- + +## 9. Migration tiers + +**P0**: `both` + merged tp/sl in `buy`/`sell` + trailing → fix first. +**P1**: Touch logic + large live/backtest drift → four-way + confirmed + edge. +**P2**: Simple two-way → keep A, add contract header. + +Optional badge: “Contract v1 certified”. + +--- + +## 10. Implementation map + +| Rule | Code | +|------|------| +| Four-way | `TradingExecutor._execute_indicator_with_prices`, `BacktestService` | +| Two-way both | `_indicator_both_mode` only for buy/sell normalization | +| Confirmed bars | `signal_mode`, `exit_signal_mode` | +| Close retry | `PendingOrderWorker`, `resolve_reduce_only_quantity` | +| Sandbox | `safe_exec.py` | + +--- + +## 11. Revision history + +| Version | Date | Notes | +|---------|------|-------| +| 1.0 | 2026-05 | Initial standard | diff --git a/multi_indicator_composite.py b/multi_indicator_composite.py new file mode 100644 index 0000000..c75e39e --- /dev/null +++ b/multi_indicator_composite.py @@ -0,0 +1,125 @@ +# ============================================================ +# 多指标组合策略(文档同步版) +# Multi-Indicator Composite Strategy (Doc-Aligned Example) +# ============================================================ +# +# 示例目标: +# 1. 演示如何把 `# @param`、`# @strategy` 和平台 UI 对齐 +# 2. 演示如何组合均线、RSI、MACD、成交量过滤 +# 3. 演示如何把原始条件整理成更稳定的边缘触发信号 +# +# ============================================================ + +my_indicator_name = "多指标组合策略" +my_indicator_description = "均线、RSI、MACD 与成交量过滤共同参与的组合信号示例。" + +# --- QuantDinger execution contract (v1) --- +# signal_form: four_way +# exit_owner: engine +# flip_mode: R2 + +# === 参数声明 === +# @param sma_short int 10 短期均线周期 +# @param sma_long int 30 长期均线周期 +# @param rsi_period int 14 RSI周期 +# @param rsi_oversold int 30 RSI超卖阈值 +# @param rsi_overbought int 70 RSI超买阈值 +# @param use_macd bool true 是否使用MACD过滤 +# @param use_volume bool false 是否使用成交量过滤 +# @param volume_mult float 1.5 成交量放大倍数 + +# === 平台默认策略配置 === +# @strategy stopLossPct 0.025 +# @strategy takeProfitPct 0.06 +# @strategy entryPct 0.2 +# @strategy trailingEnabled true +# @strategy trailingStopPct 0.02 +# @strategy trailingActivationPct 0.04 +# @strategy tradeDirection both + +# 说明:本示例使用四路执行信号;固定止损、止盈和追踪止损由引擎负责。 +# output["signals"] 只负责图表标记,不参与下单。 + +df = df.copy() + +# === 获取参数 === +sma_short_period = int(params.get('sma_short', 10)) +sma_long_period = int(params.get('sma_long', 30)) +rsi_period = int(params.get('rsi_period', 14)) +rsi_oversold = int(params.get('rsi_oversold', 30)) +rsi_overbought = int(params.get('rsi_overbought', 70)) +use_macd = bool(params.get('use_macd', True)) +use_volume = bool(params.get('use_volume', False)) +volume_mult = float(params.get('volume_mult', 1.5)) + +# === 计算均线 === +sma_short = df["close"].rolling(sma_short_period).mean() +sma_long = df["close"].rolling(sma_long_period).mean() + +# === 计算RSI === +delta = df["close"].diff() +gain = delta.where(delta > 0, 0).rolling(window=rsi_period).mean() +loss = (-delta.where(delta < 0, 0)).rolling(window=rsi_period).mean() +rs = gain / loss.replace(0, np.nan) +rsi = 100 - (100 / (1 + rs)) + +# === 计算MACD === +exp1 = df["close"].ewm(span=12, adjust=False).mean() +exp2 = df["close"].ewm(span=26, adjust=False).mean() +macd = exp1 - exp2 +macd_signal = macd.ewm(span=9, adjust=False).mean() + +# === 计算成交量均线 === +volume_ma = df["volume"].rolling(20).mean() + +# === 原始条件 === +ma_golden = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1)) +ma_death = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1)) +rsi_buy = rsi < rsi_oversold +rsi_sell = rsi > rsi_overbought +macd_up = macd > macd_signal +macd_down = macd < macd_signal +volume_up = df["volume"] > volume_ma * volume_mult + +# === 组合条件 === +raw_buy = ma_golden | rsi_buy +raw_sell = ma_death | rsi_sell + +if use_macd: + raw_buy = raw_buy & macd_up + raw_sell = raw_sell | macd_down + +if use_volume: + raw_buy = raw_buy & volume_up + +# === 四路边缘触发执行信号 === +def edge(signal): + signal = signal.fillna(False).astype(bool) + return signal & ~signal.shift(1).fillna(False) + + +open_long = edge(raw_buy) +open_short = edge(raw_sell) +df["open_long"] = open_long +df["close_short"] = open_long +df["open_short"] = open_short +df["close_long"] = open_short + +# === 买卖标记点 === +buy_marks = [df["low"].iloc[i] * 0.995 if df["open_long"].iloc[i] else None for i in range(len(df))] +sell_marks = [df["high"].iloc[i] * 1.005 if df["open_short"].iloc[i] else None for i in range(len(df))] + +# === 图表输出配置 === +output = { + "name": my_indicator_name, + "plots": [ + {"name": f"SMA{sma_short_period}", "data": sma_short.fillna(0).tolist(), "color": "#FF9800", "overlay": True}, + {"name": f"SMA{sma_long_period}", "data": sma_long.fillna(0).tolist(), "color": "#3F51B5", "overlay": True}, + {"name": "RSI", "data": rsi.fillna(50).tolist(), "color": "#722ED1", "overlay": False}, + {"name": "MACD", "data": macd.fillna(0).tolist(), "color": "#13C2C2", "overlay": False} + ], + "signals": [ + {"type": "buy", "text": "B", "data": buy_marks, "color": "#00E676"}, + {"type": "sell", "text": "S", "data": sell_marks, "color": "#FF5252"} + ] +}