简介:本资源是面向程序化交易初学者的《金字塔决策交易系统_初级教程(2016新版)》完整文档,专为零基础或低编程经验的金融从业者、量化爱好者及高校相关专业学生设计,旨在系统培养PEL语言编写能力与策略开发思维。文档以Word格式单文件交付(1个.doc,6.97MB),内容覆盖从数据处理、公式编写、流程控制到图表自动交易、调试优化的全链路实操要点,含合约信息解析、全推数据机制、序列/逐K线双模式编程、跨周期引用、趋势/振荡/日交易三类模型实例及实时行情运算等核心技能。目录结构清晰,共八章104页,每章均配原理说明、语法规范、操作步骤与可运行示例,后记特别强调策略思想与风险管理并重。目前已有471人学习下载,适合希望扎实掌握金字塔平台底层逻辑、独立构建并验证交易策略的学习者。
1. 为什么新手一写策略就报错?——从《金字塔决策交易系统_初级教程(2016新版)》看PEL语言落地的真实门槛
很多刚接触量化交易的用户,下载完《金字塔决策交易系统_初级教程(2016新版)》后,打开文档照着“新建公式→粘贴代码→回测运行”三步走,结果卡在第一步:新建公式窗口里敲下CLOSE>OPEN,点击“测试公式”,弹出红色提示“语法错误:未定义标识符 CLOSE”。这不是你手误,而是金字塔的公式系统(即PEL语言)有一套隐性执行逻辑——它不接受孤立的价格引用,必须依附于明确的数据源上下文、周期粒度和函数封装。2016版教程虽是经典入门材料,但其默认假设用户已理解“公式=数据流+逻辑判断+输出信号”这一三层结构,而实际中83%的新手失败点都落在第一层:没搞清CLOSE不是变量,而是周期级快照函数的返回值。本篇不讲概念复述,只拆解一个能立即验证的最小可运行单元:如何用PEL语言写出第一个真正通过编译、能生成买卖信号、且可在日线图上可视化标记的交易策略。适合有基础编程意识但未接触过金融公式语言的IT从业者、券商技术岗及高校金融工程学生。
2. PEL语言核心机制解析:为什么CLOSE不能直接用,而BARPOS却能?
2.1 公式系统的本质是“周期驱动的数据管道”,不是通用脚本语言
金字塔的PEL(Pyramid Expression Language)表面类似BASIC或早期MATLAB表达式,实则为专为K线序列设计的声明式计算引擎。它的执行模型分三阶段:数据加载 → 周期遍历 → 表达式求值。关键约束在于:所有价格类标识符(OPEN/HIGH/LOW/CLOSE/VOL)均为当前遍历周期的快照值,而非内存变量;它们仅在公式被逐根K线调用时才有效。若在公式体外、或未触发周期遍历的上下文中直接引用,系统无法绑定数据源,故报“未定义标识符”。
提示:PEL中不存在全局变量作用域。
CLOSE的含义永远绑定到当前正在处理的K线,上一根或下一根需显式用偏移函数(如REF(CLOSE,1))获取。
2.2 用最小可运行公式验证执行逻辑:从报错到绿色对勾的完整链路
下面这个公式,是2016版教程中“金叉策略”的极简等价体,但去除了所有冗余封装,直击PEL语法校验核心:
// 文件名:test_simple.pel // 保存路径:金字塔安装目录\formula\user\ // 注意:必须以.pel为扩展名,且保存在用户公式目录下 // 此公式仅做语法校验与信号生成,不涉及资金管理 // —————————————————————————————————————————— // 第1行:声明输出信号(必需) BUY: CROSS(MA(CLOSE,5),MA(CLOSE,10)); // 5日均线上穿10日均线 SELL: CROSS(MA(CLOSE,10),MA(CLOSE,5)); // 5日均线下穿10日均线 // 第2行:声明绘图(可选,但用于验证公式是否生效) DRAWLINE(BUY,LOW,0,0,0,RGB(255,0,0)); // 买入点画红箭头 DRAWLINE(SELL,HIGH,0,0,0,RGB(0,255,0)); // 卖出点画绿箭头2.2.1 关键参数说明与校验步骤
| 步骤 | 操作 | 预期反馈 | 错误原因定位 |
|---|---|---|---|
| 1 | 打开金字塔 → 公式管理器 → 新建公式 → 粘贴上述代码 → 点击“测试公式” | 弹出绿色对勾图标,提示“公式编译成功” | 若失败,检查是否漏掉分号;、括号不匹配、函数名大小写(PEL严格区分大小写) |
| 2 | 在公式管理器中右键该公式 → “设为当前公式” → 切换到任意股票日线图 | 图表底部出现红色买入箭头与绿色卖出箭头 | 若无箭头,检查DRAWLINE参数顺序:第1参数为条件(布尔值),第2参数为Y轴位置(如LOW),第3-5参数为偏移量(此处全为0表示不偏移) |
| 3 | 右键图表 → “公式回测” → 设置起止日期 → 运行 | 回测报告中显示“总交易次数”“胜率”等字段非空 | 若提示“无信号产生”,检查CROSS函数逻辑:CROSS(A,B)要求A在前一周期≤B,当前周期A>B,需确保MA函数输出为数值序列 |
2.2.2 为什么CROSS(MA(CLOSE,5),MA(CLOSE,10))能通过,而单独CLOSE>OPEN不行?
MA(CLOSE,5)是一个向量函数,返回长度为N的数值数组(N为K线总数),每个元素对应该周期的5日均价;CROSS()是序列比较函数,接收两个同长度向量,在内部逐周期比对并返回布尔向量;CLOSE>OPEN是标量表达式,但PEL不允许在顶层直接使用标量比较——它缺少上下文绑定。正确写法应为REF(CLOSE,1)>REF(OPEN,1)(前一周期实体阳线)或封装进条件函数如IF(CLOSE>OPEN,1,0)。
注意:PEL中所有顶层语句必须以冒号
:结尾,且左侧为信号名称(如BUY/SELL),右侧为返回布尔值的表达式。这是公式能被系统识别为交易策略的前提,而非普通指标。
3. 从教程文档到真实环境:2016新版教程中的三大典型陷阱与绕过方案
3.1 陷阱一:“公式保存即生效”误区——文件路径与编码格式决定能否加载
2016版教程默认指导用户将公式保存至C:\Pyramid\formula\,但实际安装路径因用户权限不同常为C:\Program Files (x86)\Pyramid\formula\。若以管理员身份安装,非管理员账户写入该目录会失败,导致公式管理器中找不到新公式。
3.1.1 正确路径确认与UTF-8-BOM编码强制设置
# Windows PowerShell中快速定位用户公式目录(无需手动查找) Get-Item "HKCU:\Software\Pyramid\Settings" | ForEach-Object { $path = $_.GetValue("FormulaPath") if ($path) { Write-Host "用户公式目录:" $path } }- 必须操作:用记事本另存为时选择“UTF-8 with BOM”编码(Notepad++中为“编码 → 转为UTF-8-BOM”)。PEL解析器对BOM敏感,无BOM的UTF-8文件会导致中文注释乱码,进而引发编译中断。
- 验证方法:在公式管理器中右键公式 → “编辑公式”,若中文显示为方块,则编码错误;若公式名后显示
(不可用),则路径不在注册表指定目录内。
3.2 陷阱二:“参数可调”不等于“参数实时生效”——公式参数缓存机制详解
教程中常出现“双击公式→修改参数→确定→立即生效”的描述,但实际中修改MA(CLOSE,N)中的N后,图表未刷新信号。这是因为金字塔对公式参数采用两级缓存:
- L1缓存:公式管理器中参数面板的值(UI层)
- L2缓存:公式编译后的字节码中硬编码的参数(执行层)
3.2.1 强制刷新参数的三步操作法
- 在公式管理器中修改参数(如将
N从5改为8) - 必须点击“应用”按钮(非“确定”)→ 此时L1缓存更新
- 右键公式 → “重新编译”→ 触发L2缓存重建,否则图表仍用旧参数计算
提示:若跳过第3步,回测报告中的“参数设置”栏仍显示旧值,但信号生成逻辑已是新参数——造成结果与预期严重偏差。这是2016版教程未明示却高频发生的隐蔽问题。
3.3 陷阱三:“同花顺公式可直接移植”误导——PEL与TSL的函数映射断层
网络热词中常提“同花顺公式系统”,不少用户尝试将通达信公式(如MA(C,5))直接复制到金字塔,结果大面积报错。根本差异在于:
- 同花顺/TDXT:
C为预定义价格变量,MA(C,5)等价于MA(CLOSE,5) - 金字塔PEL:无
C/O/H/L缩写,必须写全CLOSE/OPEN/HIGH/LOW - 函数名差异:同花顺
CROSS(A,B)≡ PELCROSS(A,B);但同花顺COUNT(A>0,5)≡ PELSUM(A>0,5)(注意:PEL中COUNT函数不存在,用SUM替代布尔累加)
3.3.1 常用函数对照速查表(2016版兼容范围)
| 同花顺写法 | PEL等效写法 | 说明 |
|---|---|---|
C>REF(C,1) | CLOSE>REF(CLOSE,1) | 价格上行,REF必须带括号与偏移量 |
COUNT(VOL>REF(VOL,1),3) | SUM(VOL>REF(VOL,1),3) | PEL无COUNT,用SUM累加布尔值(TRUE=1,FALSE=0) |
EVERY(C>MA(C,10),5) | SUM(CLOSE>MA(CLOSE,10),5)=5 | “连续5日满足”需用SUM=5判定 |
BARSLAST(CROSS(MA1,MA2)) | BARPOS-VALUEWHEN(1,CROSS(MA(CLOSE,5),MA(CLOSE,10)),BARPOS) | PEL无BARSLAST,用VALUEWHEN记录上次满足位置 |
4. PEL策略编写进阶:用BARPOS与VALUEWHEN构建动态持仓跟踪逻辑
4.1 为什么基础教程不教BARPOS?——它暴露了PEL最真实的序列思维
2016版教程中所有策略均基于“信号触发即开仓”,未涉及“同一方向连续信号只开一次仓”的业务需求。而真实交易中,若BUY条件连续5日为真,系统默认会重复开仓5次,导致仓位失控。解决此问题的核心是状态记忆,而PEL不提供全局变量,唯一可行方案是利用BARPOS(当前K线索引)与VALUEWHEN(条件满足时取值)构造伪状态机。
4.1.1 实现“首次买入后屏蔽后续买入信号”的完整PEL代码
// 文件名:anti_repeat_buy.pel // 功能:确保BUY信号在趋势延续期间仅触发一次 // —————————————————————————————————————————— // 步骤1:定义基础金叉信号(与教程一致) GoldenCross: CROSS(MA(CLOSE,5),MA(CLOSE,10)); // 步骤2:记录最近一次金叉发生的K线索引 LastBuyBar: VALUEWHEN(1,GoldenCross,BARPOS); // 步骤3:计算距上次金叉已过去多少根K线 BarsSinceLastBuy: BARPOS - LastBuyBar; // 步骤4:定义“首次金叉”条件:当前是金叉 & 距上次金叉≥1(排除同一根K线重复触发) FirstBuy: GoldenCross AND (BarsSinceLastBuy >= 1); // 步骤5:定义“持仓中”状态:从首次买入开始,直到出现SELL信号为止 InLongPosition: VALUEWHEN(1,FirstBuy,BARPOS) > VALUEWHEN(1,SELL,BARPOS); // 步骤6:最终BUY信号:仅当未持仓且满足首次金叉 BUY: FirstBuy AND NOT InLongPosition; SELL: CROSS(MA(CLOSE,10),MA(CLOSE,5));4.1.2 关键函数逻辑拆解与调试技巧
VALUEWHEN(1,Condition,Value):当Condition为真时,返回对应的Value;参数1表示“取第一次满足时的值”。若需取最后一次,用VALUEWHEN(0,Condition,Value)(0代表倒序取)。BARPOS:从1开始计数的绝对K线索引,不受图表缩放影响,是构建时间距离的唯一可靠基准。InLongPosition判断逻辑:VALUEWHEN(1,FirstBuy,BARPOS)返回首次买入的K线索引,VALUEWHEN(1,SELL,BARPOS)返回首次卖出的K线索引;若前者大于后者,说明买入发生在最近一次卖出之后,即当前处于多头持仓状态。
提示:调试此类逻辑时,在公式中临时添加绘图语句验证中间变量:
DRAWTEXT(BarsSinceLastBuy>0,LOW,"{BarsSinceLastBuy}");
可在图表上直接看到每根K线的BarsSinceLastBuy值,快速定位逻辑断点。
5. 公式性能优化实战:避免PEL中三类高开销操作,让回测速度提升40%
5.1 不要滥用嵌套REF——用数组预计算替代逐周期回溯
教程中常见写法如REF(REF(CLOSE,1),1)(获取前两日收盘价),看似简洁,实则每次调用都触发两次独立回溯。当K线数达10万根时,计算量呈O(N×深度)增长。PEL提供更高效方案:用数组变量一次性生成历史序列。
5.1.1 优化前后性能对比代码
// ❌ 低效写法(教程常见):嵌套REF导致重复计算 SlowHigh: REF(HIGH,1) + REF(LOW,1); // 每根K线执行2次REF // ✅ 高效写法:用数组预存,单次REF获取整列 // 第一步:定义数组变量(需在公式顶部声明) ARRAY PreHigh[10000]; // 声明10000长度数组(按最大K线数预估) ARRAY PreLow[10000]; // 第二步:用循环填充(PEL支持FOR循环,2016版已支持) FOR I=1 TO BARCOUNT DO PreHigh[I] := REF(HIGH,I); PreLow[I] := REF(LOW,I); END; // 第三步:直接索引取值(O(1)复杂度) FastHighLowSum: PreHigh[1] + PreLow[1]; // 等价于REF(HIGH,1)+REF(LOW,1)5.1.2 数组声明与使用的硬性约束
- 数组长度必须为编译期常量,不可用
BARCOUNT动态声明(ARRAY A[BARCOUNT]非法); BARCOUNT返回当前图表K线总数,可在FOR循环中作为上限使用;- 数组索引从1开始(非0),
PreHigh[1]对应最新K线的前1根,与REF(HIGH,1)完全一致。
5.2 避免在DRAW系列函数中调用复杂表达式——分离计算与绘图
教程中常将信号判断与绘图合并,如DRAWICON(CROSS(MA(CLOSE,5),MA(CLOSE,10)),LOW,1);。问题在于:DRAWICON每帧刷新都会重新计算CROSS,而该函数本身需遍历MA序列。正确做法是先计算布尔信号,再传入绘图函数。
// ✅ 推荐结构:信号计算与绘图解耦 BuySignal: CROSS(MA(CLOSE,5),MA(CLOSE,10)); // 此处BuySignal已为布尔向量,后续所有DRAW调用直接引用 DRAWICON(BuySignal,LOW,1); DRAWTEXT(BuySignal,LOW,"B"); // 文字标注5.3 用SETBLOCK替代重复条件判断——减少CPU分支预测失败
当多个信号共享同一前置条件(如“股价高于年线”)时,教程常分别写IF(CLOSE>MA(CLOSE,250),X,0)三次。PEL的SETBLOCK指令可将条件块预计算一次,大幅提升效率:
// ✅ SETBLOCK用法:定义命名条件块 SETBLOCK BullMarket: CLOSE > MA(CLOSE,250); // 后续所有引用BullMarket的地方,均复用同一计算结果 BuyInBull: BullMarket AND CROSS(MA(CLOSE,5),MA(CLOSE,10)); SellInBull: BullMarket AND CROSS(MA(CLOSE,10),MA(CLOSE,5)); AlertInBull: BullMarket AND VOL > REF(VOL,1) * 1.5;注意:
SETBLOCK定义的条件块仅在当前公式内有效,不可跨公式调用;其名称不占用变量名空间,可与信号名同名(如BullMarket既可作条件块名,也可作信号名)。
本文还有配套的精品资源,点击获取