上传文件至「/」
This commit is contained in:
@@ -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` 时,所有退出逻辑写入信号代码。
|
||||
@@ -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.
|
||||
```
|
||||
@@ -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.
|
||||
```
|
||||
@@ -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 |
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user