本文通过两个示例带你快速上手期魔方 Python 指标开发:一个极简双均线 Demo + 一个完整的 RSI 八步实战(从新建到加载)。
新手入门 Demo:双均线
该 demo 以双均线为例,步骤如下:
- 第一步是先定义指标参数 Params
- 第二步在 on_init 中定义输出图形对象
- 第三步编写 calculate_all 计算均线输出值的代码
- 第四步编写 calculate_last 计算均线输出值的代码
# 双均线案例
# 外置参数
from pydantic import BaseModel
class Params(BaseModel, validate_assignment=True):
"""开发者无需修改Params类,只需关注Params类中的字段定义即可"""
from pydantic import Field
is_main:bool = Field(default=True, title="是否是主图")
fast:int = Field(default=10, title="快线")
slow:int = Field(default=20, title="慢线")
# 指标初始化
def on_init(self):
self.calculate_type = 'last'
self.calculate_data_type = 'list'
self.fast_line = self.Line('fast_line', 'rgba(255, 0, 0, 1)')
self.slow_line = self.Line('slow_line', 'rgba(0, 255, 0, 1)')
self.fields('close')
# 全量计算方法
def calculate_all(self, data):
length, klines = data
self.length = length
self.datetime = klines['datetime']
self.close = klines['close']
fast = self.params.fast
slow = self.params.slow
fast_start = fast - 1
slow_start = slow - 1
for i in range(length):
if i >= fast_start:
fast_value = sum(self.close[i-fast_start:i+1]) / fast
self.fast_line.set_point(self.datetime[i], fast_value)
if i >= slow_start:
slow_value = sum(self.close[i-slow_start:i+1]) / slow
self.slow_line.set_point(self.datetime[i], slow_value)
# 实时行情计算增量数据
def calculate_last(self, data):
_, kline = data
if kline['datetime'] != self.datetime[-1]:
self.length += 1
self.datetime.append(kline['datetime'])
self.close.append(kline['close'])
else:
self.close[-1] = kline['close']
last_fast = self.close[-self.params.fast:]
last_slow = self.close[-self.params.slow:]
fast_value = sum(last_fast) / self.params.fast
slow_value = sum(last_slow) / self.params.slow
self.fast_line.set_point(kline['datetime'], fast_value)
self.slow_line.set_point(kline['datetime'], slow_value)
开发实战:RSI 指标八步
在这一章,我们通过编写 RSI 指标来熟悉指标开发的完整流程。
第一步:新建指标文件
登录期魔方客户端 → 点击 策略 → 点击 指标列表 → 在 我的指标文件夹的 操作中点击 新增指标
然后会弹出提示框,我们输入指标名称:my_rsi,选择语言:python,点击确定
如果您之前没有下载过编辑器,此时会提示您下载一个编辑器用于编写指标(整个过程会自动完成,请耐心等待一会)
![]()
第二步:编写指标模板代码
点击刚才创建好的 my_rsi 指标旁边的 编辑指标 按钮,此时会自动唤起编辑器,并打开一个基础 python 指标模版文件。
新的 python 指标文件并不是空白的,而是包含了一些基础模板:
#------------------------------------------#
#文件类型:技术指标
#帮助文档:https://qmfquant.com/static/doc/code/indicatorEdit.html
#期魔方,为您提供专业的量化服务
#------------------------------------------#
from pydantic import BaseModel
class Params(BaseModel, validate_assignment=True):
from pydantic import Field
is_main:bool = Field(default=True, title="是否为主图")
# def on_auth(self):
# pass
# def on_subscribe(self):
# pass
def on_init(self):
pass
def calculate_all(self, data):
length, datetime_str, open, high, low, close, volume = data
pass
def calculate_last(self, data):
length, datetime_str, open, high, low, close, volume = data
pass
第一步,对模版文件进行调整,去掉不需要的代码(权限验证、订阅数据),得到精简版本:
from pydantic import BaseModel
class Params(BaseModel, validate_assignment=True):
from pydantic import Field
is_main:bool = Field(default=True, title="是否为主图")
def on_init(self):
pass
def calculate_all(self, data):
length, datetime_str, open, high, low, close, volume = data
pass
def calculate_last(self, data):
length, datetime_str, open, high, low, close, volume = data
pass
第三步:定义外置参数(params)
由于 RSI 指标是副图指标,所以我们需要在外置参数中指定其为副图。RSI 有一个参数是 天数(周期),所以我们在外置参数中声明一个属性来表明这个参数:
class Params(BaseModel, validate_assignment=True):
from pydantic import Field
is_main:bool = Field(default=False, title="是否为主图")
# ^^^^^ 使用 False 来表明副图
period:int = Field(default=14, title="周期")
# 添加这行,声明一个类型为 int ,名称为 period 的外置参数,给一个默认值 14
第四步:实现 on_init 初始化
RSI 不需要每次都计算其历史值,所以确定其计算方式为增量计算。RSI 指标是绘制一条线条,所以我们在初始化时创建一个线条对象。RSI 仅需要收盘价就可以完成计算,所以声明只需要收盘价数据。
def on_init(self):
self.calculate_type = 'last' # 'all' - 全量计算,'last' - 增量计算
self.calculate_data_type = 'list'
self.fields('close') # 订阅收盘价
self.rsi_line = self.Line('rsi', '#f0f5ff', line_width=3) # 线条对象
第五步:实现 calculate_all 全量计算
虽然指标是以增量计算来运行的,但是需要为其初始化图案时进行一次全量计算(第一次接收到600根K线数据):
def calculate_all(self, data):
length, klines = data
self.length = length
self.datetime = klines['datetime']
self.close = klines['close']
deltas = []
for i in range(1, len(self.close)):
deltas.append(self.close[i] - self.close[i - 1])
if len(deltas) < self.params.period:
for i in range(length):
self.rsi_line.set_point(self.datetime[i], None)
up_sum = 0
down_sum = 0
for delta in deltas[:self.params.period]:
if delta > 0:
up_sum += delta
elif delta < 0:
down_sum += abs(delta)
self.avg_up = up_sum / self.params.period
self.avg_down = down_sum / self.params.period
for i in range(0, self.params.period):
self.rsi_line.set_point(self.datetime[i], None)
for i in range(self.params.period, len(deltas)):
delta = deltas[i]
up_val = max(delta, 0)
down_val = abs(min(delta, 0))
avg_up = (self.avg_up * (self.params.period - 1) + up_val) / self.params.period
avg_down = (self.avg_down * (self.params.period - 1) + down_val) / self.params.period
rs = avg_up / avg_down if avg_down != 0 else float('inf')
rsi = 100 - (100 / (1 + rs))
rsi = round(rsi, 2)
self.rsi_line.set_point(self.datetime[i+1], rsi)
if i+1 != len(deltas):
self.avg_up = avg_up
self.avg_down = avg_down
归纳为 3 步:获取数据 → 算法计算 → 写入图形对象。
第六步:实现 calculate_last 增量计算
增量计算中,用 datetime 比对当前接收到的 K 线是否为新的一根:
def calculate_last(self, data):
_, kline = data
if kline['datetime'] != self.datetime[-1]:
self.length += 1
self.datetime.append(kline['datetime'])
self.close.append(kline['close'])
self.avg_up = self.last_avg_up
self.avg_down = self.last_avg_down
else:
self.close[-1] = kline['close']
delta = self.close[-1] - self.close[-2]
up_val = max(delta, 0)
down_val = abs(min(delta, 0))
avg_up = (self.avg_up * (self.params.period - 1) + up_val) / self.params.period
avg_down = (self.avg_down * (self.params.period - 1) + down_val) / self.params.period
rs = avg_up / avg_down if avg_down != 0 else float('inf')
rsi = 100 - (100 / (1 + rs))
rsi = round(rsi, 2)
self.rsi_line.set_point(kline['datetime'], rsi)
self.last_avg_up = avg_up
self.last_avg_down = avg_down
第七步:保存编译与调试
当开发完成,保存指标(会自动触发编译),或者在编辑器中点击右键 → 选择 python指标/策略编译。
编译结果会在控制台输出。如果代码有误,控制台会提示相应的错误。比如我们把 set_point 写成了 set_points:
编译失败,失败原因:
<未通过测试数据: line 62, in calculate_all()>
错误描述:
AttributeError: 'Line' object has no attribute 'set_points'. Did you mean: 'set_point'?
编译成功则会显示 "编译成功!"
编译成功后会生成一个加密文件,该文件才是真实运行的指标。每次改动代码后,必须 保存 → 编译,才会让改动生效。
使用日志调试
def on_init(self):
self.calculate_type = 'last'
self.calculate_data_type = 'list'
self.fields('close')
self.rsi_line = self.Line('rsi', '#f0f5ff', line_width=3)
self.logger = self.Logger(r'D:\logs\my_rsi.log')
然后在 calculate_all 中记录日志:
self.logger.info(f"rsi = {rsi}")
第八步:绘制图形
编写并调试完成后,进入行情页面,选择合约,K 线图页面点击右键,指标窗口个数选择 2 个,然后在副图加载 Py_my_rsi(所有 python 指标会有 Py_ 前缀)。填写外置参数后加载指标,即可看到效果。
![]()
相关阅读

评论
登录后参与讨论,与站内账号体系共用。