ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

麦语言函数手册实战:从零构建可复现的量化指标脚本

麦语言函数手册实战:从零构建可复现的量化指标脚本

简介:麦语言函数手册是文华财经推出的程序化函数库参考文档,面向使用文华平台进行量化策略开发的交易者与编程初学者,帮助其快速查阅函数用法、理解模型编写逻辑。手册系统梳理了自编策略模型支持的各类函数,涵盖历史数据引用(如AVPRICE、SETTLE、CLOSE、HIGH、LOW、OPEN、OPI)、引用函数(REF、REFX)、导入函数(#IMPORT)、价格函数(MINPRICE)与成交量函数(VOL),并延伸至日内高频数据引用,如L2_BID1至L2_BID5、L2_ASK1至L2_ASK5等买卖盘价量函数,每项均附用法说明与示例。资源包为1个doc文档,大小约309KB,结构紧凑、便于检索。目前已有3599人学习下载,适合需要系统掌握麦语言函数体系、提升策略开发效率与代码质量的读者参考使用。

1. 麦语言函数手册:从零手搓一套可复现的指标脚本

很多人第一次接触麦语言,是在某个量化平台的公式编辑器里——想改一根均线参数,结果被一堆REF、HHV、BARSLAST卡住,翻遍平台自带的函数手册,发现它只告诉你“这个函数返回周期内的最高值”,却不告诉你当周期不足时它到底返回什么、能不能嵌套、在跨周期引用时会不会直接给你一个空值。麦语言函数手册真正的价值,不是当字典查,而是当施工图用:它决定了你写的每一个指标、每一个条件单、每一个信号过滤逻辑,能不能在实盘里稳定跑起来。这篇笔记面向两类人:一类是刚拿到平台账号、想把自己的交易想法翻译成脚本的新手;另一类是用过几个函数、但一遇到“信号闪烁”“回测漂亮实盘拉胯”就抓瞎的熟手。我会按“函数怎么分类 → 核心函数怎么用 → 参数怎么调 → 坑在哪 → 怎么验证”的顺序,把手册里那些一笔带过的边界条件全部摊开讲。

2. 麦语言函数体系拆解:从数据引用到信号输出的四层结构

麦语言的函数不是平铺的一张大表,它内部有清晰的层次。你如果按“数据获取 → 数值计算 → 逻辑判断 → 信号输出”这四层去理解,手册里那些散落的函数名就会自动归位。这一章先把体系讲透,再落到具体函数的调用方式上。

2.1 数据引用层:OPEN、CLOSE、HIGH、LOW 与 REF 的周期语义

数据引用层是所有指标的起点。麦语言里最基础的四个变量是OPEN、CLOSE、HIGH、LOW,它们分别代表当前周期的开盘价、收盘价、最高价、最低价。这里有一个新手最容易翻车的点:在盘中运行时,CLOSE并不是“收盘价”,而是“最新价”。只有K线走完,它才等于真正的收盘价。这个差异直接导致同一个公式在盘中信号和盘后信号不一致。

REF(X, N)是引用函数里最常用的一个,意思是“引用 N 个周期前的 X 值”。比如REF(CLOSE, 1)就是上一根K线的收盘价。手册里通常只写“引用若干周期前的数据”,但不会强调:当 N 大于当前已有的K线数量时,返回值是无效值(通常表现为空或0),而不是报错。这意味着你在上市初期的新品种上跑长周期均线,前面几十根K线的计算结果全是无效的。

# 模拟麦语言 REF 的周期引用逻辑(Python 伪代码,便于理解边界) def ref(series, n): """ series: 数据序列,按时间从旧到新排列 n: 向前引用的周期数 返回:与 series 等长的列表,前 n 个位置为 None(无效值) """ result = [None] * len(series) for i in range(n, len(series)): result[i] = series[i - n] return result # 示例:对收盘价序列做 REF(CLOSE, 1) close = [10.0, 10.2, 10.1, 10.5, 10.3] prev_close = ref(close, 1) # 输出:[None, 10.0, 10.2, 10.1, 10.5]

上面这段伪代码说明了REF的边界行为:前 N 个周期没有足够的历史数据,返回无效值。在麦语言里,这个无效值参与后续计算时,可能导致整个条件判断失效。常见做法是配合ISNULL或VALID类函数做过滤,或者在公式开头用BARSCOUNT(CLOSE) > N做前置判断。

参数设置上,REF的第二个参数 N 必须是正整数常量,不能是变量序列。如果你需要动态周期引用,得用REFX或循环结构变通,但那样会显著增加计算量。我一般会在公式里把 N 控制在 1 到 60 之间,超过 60 的引用尽量改用MA或SUM这类聚合函数替代,减少逐周期回溯的开销。

2.2 数值计算层:MA、EMA、SUM 与 STD 的选型差异

数值计算层负责把原始价格序列转换成有意义的指标值。麦语言里最常被混用的四个函数是MA、EMA、SUM、STD。它们看起来都是“算一个数”,但背后的数学定义和适用场景完全不同。

MA(X, N)是简单移动平均,把最近 N 个周期的 X 值加起来除以 N。它的特点是每个周期的权重相等,对历史数据的变化反应迟钝。EMA(X, N)是指数移动平均,近期数据权重更高,反应更灵敏。手册里通常只给公式,不告诉你:在同样的 N 值下,EMA的“等效窗口”大约是MA的 2 倍左右。也就是说,EMA(CLOSE, 10)的平滑程度接近MA(CLOSE, 20)。如果你从MA切换到EMA而不调整参数,信号频率会明显变化。

SUM(X, N)是周期求和,常用来计算成交量均线或累计涨幅。STD(X, N)是标准差,用于布林带、波动率通道等指标。这里有一个参数陷阱:STD的 N 如果设为 1,标准差为 0,布林带上下轨会重合,导致信号完全失效。我一般把STD的 N 设为 20 或 26,和MA的周期保持一致。

# 对比 MA 与 EMA 在相同 N 下的响应差异 def ma(series, n): result = [None] * len(series) for i in range(n - 1, len(series)): window = series[i - n + 1 : i + 1] result[i] = sum(window) / n return result def ema(series, n): result = [None] * len(series) alpha = 2 / (n + 1) # 初始值用第一个有效数据填充 result[n - 1] = sum(series[:n]) / n for i in range(n, len(series)): result[i] = alpha * series[i] + (1 - alpha) * result[i - 1] return result close = [10 + i * 0.1 for i in range(30)] ma10 = ma(close, 10) ema10 = ema(close, 10) # 对比最后几个值:ema10 会比 ma10 更贴近最新价格

选型建议:趋势型指标(如均线交叉)优先用EMA,因为它的滞后更小;震荡型指标(如布林带中轨)优先用MA,因为它的统计意义更直观。SUM适合成交量类累计值,STD适合波动率类指标。不要在一个公式里混用MA和EMA做同一根均线的比较,那样参数没有可比性。

2.3 逻辑判断层:CROSS、IF、BARSLAST 的组合用法

逻辑判断层是把指标值转换成交易信号的关键。CROSS(A, B)表示 A 上穿 B,返回布尔值。IF(COND, X, Y)是条件分支,COND 为真时返回 X,否则返回 Y。BARSLAST(COND)返回上一次 COND 为真到现在的周期数。这三个函数组合起来,能表达绝大多数信号逻辑。

CROSS有一个容易被忽略的细节:它判断的是“当前周期 A > B 且上一周期 A <= B”。如果 A 和 B 在上一周期相等,当前周期 A 略大于 B,CROSS也会触发。这在均线粘合后发散时会产生大量假信号。常见做法是加一个最小差值过滤,比如CROSS(MA5, MA10) AND (MA5 - MA10) > 0.01 * CLOSE。

BARSLAST的返回值在条件从未满足时是无效值,不是0。如果你用BARSLAST(COND) > 5做过滤,在 COND 从未满足的情况下,这个判断可能直接失效。稳妥的写法是BARSLAST(COND) > 5 AND BARSLAST(COND) < 1000,用一个上界把无效值排除掉。

# 模拟 CROSS 与 BARSLAST 的组合信号 def cross(a, b): signals = [False] * len(a) for i in range(1, len(a)): if a[i] > b[i] and a[i-1] <= b[i-1]: signals[i] = True return signals def barslast(cond): result = [None] * len(cond) last_true = None for i in range(len(cond)): if cond[i]: last_true = i if last_true is not None: result[i] = i - last_true return result # 示例:金叉后 5 个周期内不再重复触发 ma5 = [10, 10.1, 10.3, 10.5, 10.4, 10.2, 10.0] ma10 = [10.2, 10.2, 10.2, 10.3, 10.3, 10.3, 10.3] golden = cross(ma5, ma10) since_golden = barslast(golden) # 过滤:只在金叉当天或金叉后第 5 天之后才允许新信号 filtered = [golden[i] and (since_golden[i] == 0 or since_golden[i] > 5) for i in range(len(golden))]

参数说明:CROSS的两个参数必须是序列,不能是常量。IF的嵌套层数建议不超过 3 层,否则可读性急剧下降。BARSLAST的参数是布尔序列,返回值是周期数,单位与K线周期一致。如果你在日线图上用BARSLAST,返回值就是天数;在分钟图上就是分钟数。

2.4 信号输出层:DRAWICON、PLOT 与条件单的对接方式

信号输出层决定你的指标在图表上怎么显示,以及能不能被条件单系统识别。DRAWICON(COND, PRICE, TYPE)在满足 COND 的K线位置画图标,PLOT(X, NAME)画曲线。这两个函数主要影响可视化,不直接影响交易。真正和交易对接的是条件单里的信号引用,通常用SIGNAL或BUY/SELL类关键字。

这里有一个实盘和回测差异的根源:图表上的DRAWICON是“事后确认”的,K线走完才画;而条件单是“实时触发”的,盘中价格触及就执行。如果你用CLOSE作为触发价,盘中CLOSE是变动的,可能导致同一根K线内反复触发。常见做法是用HIGH或LOW作为触发价,或者加一个BARSTATUS判断当前K线是否已走完。

# 模拟信号输出与条件单对接的过滤逻辑 def generate_signal(close, ma_fast, ma_slow, bar_status): """ bar_status: 0 表示K线未走完,1 表示已走完 只在K线走完时输出信号,避免盘中反复触发 """ signals = [] for i in range(len(close)): if bar_status[i] == 1 and ma_fast[i] > ma_slow[i] and ma_fast[i-1] <= ma_slow[i-1]: signals.append({"bar": i, "action": "BUY", "price": close[i]}) else: signals.append(None) return signals

参数说明:DRAWICON的 TYPE 参数在不同平台取值不同,常见的是 1 到 20 的整数,对应不同图标。PLOT的 NAME 是字符串,会显示在图表图例里。条件单对接时,信号名称必须和平台后台配置的一致,大小写敏感。我一般会在公式末尾加一行SIGNAL: COND, PRICE;作为统一输出口,方便后续切换条件单类型。

3. 用麦语言函数手册跑通第一个自定义指标:从公式到图表

这一章用一个完整的例子,把前面讲的四层结构串起来。目标是写一个“均线趋势过滤 + 波动率突破”的指标,能在图表上显示买卖点,并且能导出信号用于回测。

3.1 需求拆解:均线趋势过滤 + 波动率突破的信号定义

需求很具体:当短期均线在长期均线上方,且价格突破过去 20 个周期的最高价时,产生买入信号;当短期均线在长期均线下方,且价格跌破过去 20 个周期的最低价时,产生卖出信号。同时,用 ATR 类波动率指标过滤掉波动过小的信号,避免在横盘震荡里反复被扫。

这个需求涉及四个函数:MA算均线,HHV算周期最高价,LLV算周期最低价,ATR算波动率。HHV(HIGH, N)返回 N 周期内最高价的最大值,LLV(LOW, N)返回 N 周期内最低价的最小值。ATR在麦语言里通常用MA(MAX(MAX(HIGH-LOW, ABS(HIGH-REF(CLOSE,1))), ABS(LOW-REF(CLOSE,1))), N)来近似。

3.2 公式编写:变量命名、参数区与计算区的分离

麦语言公式一般分三段:参数区、变量区、计算区。参数区用INPUT声明可调参数,变量区用VAR声明中间变量,计算区写逻辑。下面是一个可运行的框架。

# 麦语言公式框架(伪代码,展示结构) # 参数区 INPUT: FAST_N(5, 1, 60), SLOW_N(20, 1, 120), BREAK_N(20, 1, 60), ATR_N(14, 1, 60), ATR_MIN(0.005, 0.001, 0.05); # 变量区 VAR: MA_FAST, MA_SLOW, HH, LL, ATR_VAL, COND_BUY, COND_SELL; # 计算区 MA_FAST = MA(CLOSE, FAST_N); MA_SLOW = MA(CLOSE, SLOW_N); HH = HHV(HIGH, BREAK_N); LL = LLV(LOW, BREAK_N); ATR_VAL = MA(MAX(MAX(HIGH - LOW, ABS(HIGH - REF(CLOSE, 1))), ABS(LOW - REF(CLOSE, 1))), ATR_N); # 信号条件 COND_BUY = MA_FAST > MA_SLOW AND CLOSE > HH AND ATR_VAL / CLOSE > ATR_MIN; COND_SELL = MA_FAST < MA_SLOW AND CLOSE < LL AND ATR_VAL / CLOSE > ATR_MIN; # 输出 DRAWICON(COND_BUY, LOW, 1); DRAWICON(COND_SELL, HIGH, 2); PLOT(MA_FAST, "MA_FAST"); PLOT(MA_SLOW, "MA_SLOW");

逻辑说明:INPUT声明了五个参数,括号里依次是默认值、最小值、最大值。VAR声明了中间变量,避免重复计算。COND_BUY同时满足三个条件:短期均线在长期均线上方、收盘价突破 20 周期最高价、ATR 占价格比例大于阈值。DRAWICON在买入信号位置画图标 1,卖出信号画图标 2。

参数说明:FAST_N默认 5,适合短线;SLOW_N默认 20,适合中短线。BREAK_N默认 20,和SLOW_N一致,减少参数数量。ATR_MIN默认 0.005,即 0.5%,过滤掉波动过小的信号。如果品种波动大,可以调到 0.01;如果波动小,调到 0.003。

3.3 参数调优:FAST_N、SLOW_N、BREAK_N 的敏感度测试

参数调优不是随便试几个数,而是要有方法。我一般用“单参数扫描 + 固定其他参数”的方式,观察信号数量和胜率的变化。下面是一个简单的扫描脚本框架。

# 参数扫描框架(Python 伪代码,用于理解调优逻辑) def scan_fast_n(close, high, low, fast_range, slow_n=20, break_n=20): results = [] for fast_n in fast_range: signals = generate_signals(close, high, low, fast_n, slow_n, break_n) win_rate = backtest(signals, close) results.append({"fast_n": fast_n, "signal_count": len(signals), "win_rate": win_rate}) return results # 示例:扫描 FAST_N 从 3 到 15 scan_results = scan_fast_n(close, high, low, range(3, 16)) # 观察 signal_count 和 win_rate 的平衡点

调优原则:FAST_N越小,信号越多,但假信号也越多;FAST_N越大,信号越少,但滞后越大。SLOW_N和BREAK_N一般保持 2 到 4 倍于FAST_N的关系。如果FAST_N调到 10 以上,SLOW_N至少要 30,否则均线交叉会非常频繁。BREAK_N不宜小于FAST_N,否则突破条件太容易满足,失去过滤意义。

3.4 信号导出:把指标条件转成可回测的信号序列

图表上的图标不能直接回测,需要把条件转成信号序列。麦语言里通常用SIGNAL关键字或者导出 CSV 后在外部回测。下面是一个导出信号序列的示例。

# 导出信号序列(伪代码) VAR: SIGNAL_TYPE; SIGNAL_TYPE = IF(COND_BUY, 1, IF(COND_SELL, -1, 0)); # 在公式末尾输出 SIGNAL: SIGNAL_TYPE, CLOSE;

逻辑说明:SIGNAL_TYPE为 1 表示买入,-1 表示卖出,0 表示无信号。SIGNAL行把信号类型和触发价格一起输出,平台回测引擎会按这个序列模拟成交。参数说明:SIGNAL的第一个参数是信号类型,第二个参数是触发价格。触发价格用CLOSE表示按收盘价成交,用HIGH或LOW表示按最高价或最低价成交。回测时要注意滑点和手续费设置,否则结果会偏乐观。

4. 麦语言函数手册避坑指南:五个让回测和实盘分道扬镳的细节

这一章集中讲踩坑记录。每一条都是我在实际项目里遇到过的,按“现象 → 原因 → 解决”写。

4.1 坑一:REF 引用未来数据导致回测虚高

现象:回测曲线非常漂亮,年化收益高得离谱,但实盘一跑就亏。原因:公式里用了REF(CLOSE, -1)或类似写法,引用了未来周期的数据。麦语言里REF的第二个参数如果是负数,表示向后引用,也就是引用未来数据。回测引擎在历史数据上会“偷看”未来价格,导致信号提前触发。解决:检查所有REF调用,确保第二个参数为正整数。如果确实需要引用未来数据做其他计算,用REFX并明确标注,但不要用于交易信号。

4.2 坑二:HHV/LLV 在周期不足时返回无效值

现象:新品种上市初期,指标值异常,信号乱跳。原因:HHV(HIGH, 20)在K线数量不足 20 根时,返回的是无效值或 0,导致后续比较逻辑出错。解决:在公式开头加BARSCOUNT(CLOSE) >= 20的前置条件,或者用IF(BARSCOUNT(CLOSE) < 20, CLOSE, HHV(HIGH, 20))做兜底。常见做法是把HHV和LLV的周期参数设为变量,根据BARSCOUNT动态调整。

4.3 坑三:CROSS 在均线粘合时频繁触发假信号

现象:均线走平时,买卖信号密集出现,手续费吃掉大部分利润。原因:CROSS只判断上穿和下穿,不判断差值大小。当两条均线数值接近时,微小波动就会触发交叉。解决:加最小差值过滤,比如CROSS(MA5, MA10) AND ABS(MA5 - MA10) / CLOSE > 0.002。或者改用MA5 > MA10 AND REF(MA5, 1) <= REF(MA10, 1)并配合BARSLAST限制信号间隔。

4.4 坑四:盘中 CLOSE 变动导致条件单反复触发

现象:同一根K线内,条件单被触发多次,成交价格混乱。原因:盘中CLOSE是最新价,价格波动时CLOSE > HH可能反复为真。解决:在条件单里加BARSTATUS = 1判断,只在K线走完后触发。或者用HIGH作为触发价,并设置“每根K线只触发一次”的平台选项。如果平台不支持,就在公式里用BARSLAST(COND_BUY) > 0做去重。

4.5 坑五:参数过度拟合导致样本外失效

现象:在历史数据上调出的“最优参数”,换到另一段行情就失效。原因:参数扫描时只看了总收益,没有看信号分布和最大回撤。解决:把数据分成训练集和验证集,训练集调参,验证集确认。参数数量控制在 3 个以内,每个参数的取值范围不要太大。我一般会保留 2 到 3 组参数,分别对应趋势行情和震荡行情,实盘时根据市场状态切换。

5. 进阶技巧:用函数手册里的冷门函数做信号过滤与仓位管理

这一章讲两个进阶用法。第一个是用FILTER函数做信号去重,第二个是用TOPRANGE或BARSCOUNT做动态仓位管理。

FILTER(COND, N)的作用是:当 COND 为真时,返回真,并在接下来的 N 个周期内屏蔽后续的 COND 信号。这个函数在手册里通常只有一句话说明,但实际用起来非常顺手。比如FILTER(CROSS(MA5, MA10), 5)表示金叉后 5 个周期内不再触发新的金叉信号。参数 N 一般设为信号平均间隔的一半左右。如果信号平均每 10 根K线出现一次,N 设为 5 比较合适。

# 用 FILTER 做信号去重 def filter_signal(cond, n): result = [False] * len(cond) cooldown = 0 for i in range(len(cond)): if cooldown > 0: cooldown -= 1 continue if cond[i]: result[i] = True cooldown = n return result # 示例:金叉信号去重,冷却期 5 个周期 golden = cross(ma5, ma10) filtered_golden = filter_signal(golden, 5)

仓位管理方面,BARSCOUNT(CLOSE)返回从第一根K线到当前的周期数。这个值可以用来做“上市初期减仓”的逻辑:当BARSCOUNT(CLOSE) < 60时,仓位系数设为 0.5;当BARSCOUNT(CLOSE) >= 60时,仓位系数设为 1.0。这样在新品种波动剧烈时自动降低风险暴露。

# 动态仓位系数 VAR: POS_RATIO; POS_RATIO = IF(BARSCOUNT(CLOSE) < 60, 0.5, 1.0); # 在信号输出时乘以仓位系数 SIGNAL: SIGNAL_TYPE * POS_RATIO, CLOSE;

验证方法:把加了过滤和仓位管理的信号序列,和原始信号序列做对比回测。重点看三个指标:信号数量减少比例、最大回撤变化、夏普比率变化。如果信号数量减少 30% 以上,但夏普比率没有提升,说明过滤条件太严,可能把有效信号也滤掉了。我一般会保留过滤前后的两套信号,实盘时先跑过滤后的,观察一段时间再决定是否切换。

最后说一个我自己的习惯:每次写完一个指标,先不急着上实盘,而是把最近 3 年的数据分成 6 段,每段半年,逐段跑一遍。如果某一段的表现明显偏离其他段,就回去检查那段时间的市场状态和参数适应性。这个习惯帮我省掉了不少后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进