Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
7.4 KiB
QuantDinger Python 策略开发指南
本指南将详细介绍如何在 QuantDinger 平台中使用 Python 开发交易策略。QuantDinger 提供了灵活的执行环境,支持数据访问、指标计算和信号生成。
1. 概览
QuantDinger 中的策略基于 信号提供者 (Signal Provider) 模式运行。系统执行你的 Python 脚本,该脚本负责处理市场数据(DataFrame)并输出交易信号。
执行流程如下:
- 输入:系统将包含 OHLCV 数据的
df(Pandas DataFrame) 注入到你的脚本环境中。 - 处理:你使用 Python (
pandas,numpy) 计算指标并定义buy/sell逻辑。 - 输出:你构造一个特定的
output字典,包含绘图数据和信号。
2. 环境与数据
你的脚本运行在一个沙盒化的 Python 环境中。
2.1 预导入库
以下库默认可用(无需 import):
pd(pandas)np(numpy)
2.2 输入数据 (df)
一个名为 df 的 Pandas DataFrame 变量会自动存在于全局作用域中。它包含所选代码和时间周期的历史市场数据。
列 (Columns):
time: 时间戳 (datetime 或 int,视上下文而定)open: 开盘价 (float)high: 最高价 (float)low: 最低价 (float)close: 收盘价 (float)volume: 成交量 (float)
示例:
# 获取收盘价序列
closes = df['close']
# 计算简单移动平均线 (SMA)
sma_20 = df['close'].rolling(20).mean()
3. 开发策略
一个标准的策略脚本包含三个部分:
- 指标计算:计算技术指标。
- 信号生成:定义买入和卖出信号逻辑。
- 输出构建:格式化结果以供图表展示和执行引擎使用。
3.1 指标计算
你可以使用标准的 Pandas 操作来计算指标。
# 示例:MACD 计算
short_window = 12
long_window = 26
signal_window = 9
ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
macd = ema12 - ema26
signal_line = macd.ewm(span=signal_window, adjust=False).mean()
3.2 信号生成 (关键)
你 必须 在 df 中创建(或作为独立变量)两个布尔类型的 Series,分别命名为 buy 和 sell。
True表示触发信号。False表示无信号。
重要:边缘触发 (Edge Triggering) 为了避免在连续的 K 线上重复发出信号(这可能导致重复下单,取决于后端配置),最佳实践是使用 边缘触发 信号(即只在条件变真的那一刻发出信号)。
# 条件:收盘价上穿 SMA 20
condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
# 条件:收盘价下穿 SMA 20
condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
# 赋值给 df (回测必需)
df['buy'] = condition_buy.fillna(False)
df['sell'] = condition_sell.fillna(False)
关于信号类型的说明:
- QuantDinger 会根据你的策略配置(仅做多、仅做空或双向)来标准化信号。
- 你的脚本只需输出 "buy"(看涨意图)或 "sell"(看跌意图)。后端会处理开仓/平仓逻辑。
3.3 可视化标记
为了在图表上展示,你通常希望将信号图标放在 K 线的上方或下方。
# 将买入标记放在最低价下方 0.5% 处
buy_marks = [
df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
for i in range(len(df))
]
# 将卖出标记放在最高价上方 0.5% 处
sell_marks = [
df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
for i in range(len(df))
]
3.4 output 变量 (必须)
最后一步是将一个字典赋值给变量 output。这告诉前端如何绘图,以及告诉后端信号在哪里。
结构:
output = {
"name": "我的策略名称",
"plots": [ ... ], # 要绘制的线条/指标列表
"signals": [ ... ] # 信号标记列表
}
Plots Schema (绘图配置):
name: 图例名称 (例如 "SMA 20")data: 数值列表 (必须与df长度一致)。使用.tolist()转换。color: 十六进制颜色字符串 (例如 "#ff0000")。overlay:True表示绘制在主图(价格图)上,False表示绘制在副图(如 RSI/MACD)。
Signals Schema (信号配置):
type: 必须是 "buy" 或 "sell"。text: 图标上显示的文本 (例如 "B", "S")。data: 数值列表 (价格位置)。无信号处为None。color: 图标颜色。
4. 完整示例:双均线交叉 (Dual SMA)
以下是一个完整的、可复制的双均线策略示例:当 SMA(10) 上穿 SMA(30) 时买入,下穿时卖出。
# 1. 指标计算
# -----------------------
# 计算短期和长期 SMA
sma_short = df['close'].rolling(10).mean()
sma_long = df['close'].rolling(30).mean()
# 2. 信号逻辑
# -----------------------
# 买入:短期 SMA 上穿 长期 SMA
raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
# 卖出:短期 SMA 下穿 长期 SMA
raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
# 清理 NaN 并确保布尔类型
buy = raw_buy.fillna(False)
sell = raw_sell.fillna(False)
# 赋值给 df 列 (后端执行的关键)
df['buy'] = buy
df['sell'] = sell
# 3. 可视化格式化
# -----------------------
# 计算标记位置
buy_marks = [
df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
for i in range(len(df))
]
sell_marks = [
df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
for i in range(len(df))
]
# 4. 最终输出
# -----------------------
output = {
'name': '双均线策略',
'plots': [
{
'name': 'SMA 10',
'data': sma_short.fillna(0).tolist(),
'color': '#1890ff',
'overlay': True
},
{
'name': 'SMA 30',
'data': sma_long.fillna(0).tolist(),
'color': '#faad14',
'overlay': True
}
],
'signals': [
{
'type': 'buy',
'text': '买',
'data': buy_marks,
'color': '#00E676'
},
{
'type': 'sell',
'text': '卖',
'data': sell_marks,
'color': '#FF5252'
}
]
}
5. 最佳实践与故障排除
5.1 处理 NaN
滚动计算(如 rolling(14))会在数据开头产生 NaN 值。
- 规则:生成信号前必须处理
NaN。 - 修复:使用
.fillna(0)或.fillna(False)。
5.2 未来函数 (Look-ahead Bias)
系统基于 K 线 收盘 时产生的信号执行交易。
- 回测引擎通常在 下一根 K 线的开盘价 执行订单。
- 你的信号逻辑应依赖
close(当前已完成的 K 线) 或shift(1)(前一根 K 线)。切勿使用shift(-1)。
5.3 性能
避免在计算逻辑中遍历 DataFrame 行 (for i in range(len(df)): ...)。这非常慢。
- 错误:使用循环计算 SMA。
- 正确:使用
df['close'].rolling(...)。 - 例外:构建
buy_marks/sell_marks列表通常需要列表推导式,这是可接受的(仅用于可视化输出)。
5.4 调试
由于在某些执行模式下无法轻易看到 print() 输出,如果策略加载失败,请检查后端日志 (backend_api_python/logs/app.log)。
- 常见错误:
KeyError(列名错误)。 - 常见错误:
ValueError(数组长度不一致)。确保plots中的数据长度与df一致。