[TOC]
欢迎来到FMZ量化交易平台!这份手册将带你从零开始,系统学习如何在FMZ平台上编写量化交易策略。无论你是编程新手还是有一定基础的开发者,这份手册都将帮助你快速上手,并最终能够独立编写简单的交易策略。
FMZ(发明者量化)是一个专业的量化交易平台,为用户提供策略编写、回测、模拟盘和实盘交易等一站式服务。平台支持连接全球主流加密货币交易所,让你可以用代码实现自动化交易。
FMZ平台支持多种编程语言编写策略:
| 语言 | 特点 | 适合人群 |
|---|---|---|
| JavaScript | 语法简洁、易上手、运行效率高 | 推荐新手首选 |
| Python | 生态丰富、数据分析能力强 | 有Python基础的用户 |
| C++ | 执行速度最快 | 对性能有极致要求的用户 |
| PINE语言 | 兼容TradingView Pine Script,轻巧而功能强大 | 熟悉TradingView的用户 |
本手册说明:为了让你最快速地入门,本手册以 JavaScript 为例进行讲解。JavaScript 语法简单直观,非常适合初学者。如果你使用其他语言,核心概念和API函数都是相同的,只需参考语法手册中对应语言的示例即可。
理解基本结构 → 熟悉常用API → 编写简单策略 → 回测验证 → 模拟盘运行 → 实盘交易
重要提醒:在你完全熟悉平台和策略之前,请务必先在回测系统和模拟盘中充分测试,不要急于实盘交易!
FMZ提供了功能完善的在线策略编辑器,无需安装任何软件,打开浏览器即可开始编写策略。
进入编辑器的步骤: 1. 登录 FMZ官网 2. 点击顶部导航栏的「策略库」 3. 点击「新建策略」按钮 4. 选择编程语言(推荐JavaScript) 5. 开始编写你的第一个策略
编辑器主要功能: - 代码高亮和自动补全 - 实时语法检查 - 策略参数配置面板 - 交互控件设置区域 - 一键回测功能
创建策略后,你可以: - 保存策略:点击「保存」按钮,策略会保存在云端 - 版本管理:每次保存都会生成历史版本,方便回溯 - 策略分类:可以为策略添加标签,便于管理
FMZ提供了三种主要的调试方式:
| 调试方式 | 说明 | 使用场景 |
|---|---|---|
| 调试工具 | 快速测试代码片段,查看API返回值 | 验证单个函数的行为 |
| 回测系统 | 用历史数据模拟策略运行 | 验证策略逻辑、评估收益 |
| 模拟盘 | 连接交易所的模拟账户实时运行 | 验证策略在真实环境中的表现 |
每个FMZ策略都必须有一个 main() 函数,这是策略的入口点。当策略启动时,系统会自动调用这个函数。
function main() {
// 你的策略代码写在这里
Log("Hello, FMZ!")
}
量化策略通常需要持续监控市场,因此大多数策略采用「轮询」架构——在一个无限循环中不断获取行情、判断条件、执行交易。
function main() {
while (true) { // 无限循环
var ticker = exchange.GetTicker() // 获取行情
Log("当前价格:", ticker.Last) // 输出信息
Sleep(1000) // 休眠1秒(1000毫秒)
}
}
关键点:
Sleep()函数非常重要!它控制每次轮询的间隔时间,避免过于频繁地请求交易所接口。建议至少设置1000毫秒(1秒)以上。
FMZ策略有完整的生命周期管理,你可以通过特定函数在不同阶段执行代码:
| 函数 | 触发时机 | 用途 |
|---|---|---|
init() |
策略启动时,main()执行前 | 初始化变量、设置参数 |
main() |
策略主体 | 核心交易逻辑 |
onexit() |
策略正常停止时 | 清理工作、撤销未完成订单 |
onerror() |
策略异常时(仅JS支持) | 错误处理 |
完整的生命周期示例:
// 初始化函数(可选)
function init() {
Log("策略初始化完成")
}
// 主函数(必须)
function main() {
Log("策略开始运行")
while (true) {
// 你的交易逻辑
Sleep(1000)
}
}
// 退出清理函数(可选)
function onexit() {
Log("策略停止,执行清理工作")
}
在编写策略时,合理使用变量非常重要:
// 全局变量:在函数外部定义,整个策略都可以访问
var totalProfit = 0
var tradeCount = 0
function main() {
while (true) {
// 局部变量:在函数内部定义,只在当前作用域有效
var ticker = exchange.GetTicker()
var currentPrice = ticker.Last
// 全局变量可以在循环中持续累加
tradeCount++
LogStatus("交易次数:", tradeCount, "当前价格:", currentPrice)
Sleep(1000)
}
}
「策略参数」是让你的策略变得灵活可配置的关键特性。通过参数化,你可以: - 在不修改代码的情况下调整策略行为 - 方便地进行参数优化和回测对比 - 让策略适应不同的市场环境
没有参数化 vs 参数化的对比:
// ❌ 硬编码(不灵活)
function main() {
var amount = 0.01 // 交易数量写死在代码里
var stopLoss = 0.05 // 止损比例写死在代码里
// ...
}
// ✅ 参数化(灵活)
// 在界面上设置参数:Amount = 0.01, StopLoss = 0.05
function main() {
Log("交易数量:", Amount) // Amount 是界面参数,可随时调整
Log("止损比例:", StopLoss) // StopLoss 是界面参数
// ...
}
在策略编辑页面下方的「策略参数」区域,你可以添加不同类型的参数:
| 类型 | 说明 | 代码中的值类型 | 使用场景 |
|---|---|---|---|
| 数字型(number) | 数值输入框 | 数字 | 交易数量、价格、周期等 |
| 字符串(string) | 文本输入框 | 字符串 | 交易对名称、备注等 |
| 布尔型(true/false) | 开关控件 | true/false | 功能开关 |
| 下拉框(selected) | 选项列表 | 索引值或绑定值 | 选择模式、方向等 |
| 加密串 | 加密输入框 | 字符串 | API密钥等敏感信息 |
步骤1:在界面添加参数
在策略编辑器下方的「策略参数」表格中添加一行:
- 变量:TradeAmount(这将成为代码中的全局变量名)
- 描述:交易数量(显示给用户看的名称)
- 类型:数字型(number)
- 默认值:0.01
步骤2:在代码中直接使用
// TradeAmount 在界面设置后,自动成为全局变量
function main() {
Log("设置的交易数量是:", TradeAmount)
// 直接使用参数进行交易
var ticker = exchange.GetTicker()
exchange.Buy(ticker.Sell, TradeAmount)
}
下拉框参数特别适合需要在几个选项中选择的场景:
界面设置:
- 变量:Direction
- 描述:交易方向
- 类型:下拉框(selected)
- 默认值:做多|做空|双向(用 | 分隔选项)
代码使用:
function main() {
// Direction 的值是选项的索引:0=做多, 1=做空, 2=双向
if (Direction == 0) {
Log("当前设置为做多模式")
} else if (Direction == 1) {
Log("当前设置为做空模式")
} else {
Log("当前设置为双向模式")
}
}
当策略参数较多时,可以使用「组件配置」中的「分组」功能,将相关参数归类显示,使界面更加清晰。
/*
界面参数设置:
- Symbol (字符串): BTC_USDT // 交易对
- GridCount (数字): 10 // 网格数量
- GridSpacing (数字): 100 // 网格间距(USDT)
- AmountPerGrid (数字): 0.001 // 每格交易量
- EnableBuy (布尔): true // 是否开启买入
- EnableSell (布尔): true // 是否开启卖出
*/
function main() {
Log("=== 网格策略参数 ===")
Log("交易对:", Symbol)
Log("网格数量:", GridCount)
Log("网格间距:", GridSpacing, "USDT")
Log("每格交易量:", AmountPerGrid)
Log("买入开关:", EnableBuy ? "开启" : "关闭")
Log("卖出开关:", EnableSell ? "开启" : "关闭")
// 根据参数执行策略逻辑...
}
「策略交互」让你可以在策略运行过程中与之”对话”——通过点击按钮或输入数据来触发特定操作。这对于以下场景非常有用: - 手动触发买入/卖出操作 - 动态修改策略参数 - 紧急情况下的干预操作 - 查询特定信息
在策略编辑器的「策略交互」区域,你可以添加以下类型的控件:
| 控件类型 | 说明 | GetCommand()收到的消息格式 |
|---|---|---|
| 按钮(button) | 纯按钮,无输入 | 控件名 |
| 数字型(number) | 按钮+数字输入框 | 控件名:数值 |
| 字符串(string) | 按钮+文本输入框 | 控件名:文本 |
| 布尔型(boolean) | 按钮+开关 | 控件名:true/false |
| 下拉框(selected) | 按钮+下拉选择 | 控件名:选项索引 |
GetCommand() 是接收交互命令的核心函数:
function main() {
while (true) {
// 获取交互命令
var cmd = GetCommand()
// 如果有命令传入
if (cmd) {
Log("收到交互命令:", cmd)
}
Sleep(1000)
}
}
返回值说明:
- 没有命令时返回空字符串 ""
- 有命令时返回格式为 控件名:数据 或 控件名(纯按钮)
当交互控件带有输入数据时,需要解析命令:
function main() {
while (true) {
var cmd = GetCommand()
if (cmd) {
// 按冒号分割命令
var arr = cmd.split(":")
var action = arr[0] // 控件名称
var value = arr[1] // 输入的值(如果有)
if (action == "buy") {
Log("执行买入,数量:", value)
} else if (action == "sell") {
Log("执行卖出,数量:", value)
}
}
Sleep(500)
}
}
假设你在「策略交互」中添加了以下控件:
- buy:数字型,描述为”买入”
- sell:数字型,描述为”卖出”
- cancelAll:按钮型,描述为”撤销所有订单”
function main() {
while (true) {
var cmd = GetCommand()
if (cmd) {
var arr = cmd.split(":")
var action = arr[0]
if (action == "buy" && arr[1]) {
var amount = parseFloat(arr[1])
var ticker = exchange.GetTicker()
Log("手动买入,价格:", ticker.Sell, "数量:", amount)
exchange.Buy(ticker.Sell, amount)
}
else if (action == "sell" && arr[1]) {
var amount = parseFloat(arr[1])
var ticker = exchange.GetTicker()
Log("手动卖出,价格:", ticker.Buy, "数量:", amount)
exchange.Sell(ticker.Buy, amount)
}
else if (action == "cancelAll") {
Log("撤销所有未完成订单")
var orders = exchange.GetOrders()
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id)
}
}
}
// 显示状态
var account = exchange.GetAccount()
LogStatus("可用余额:", account.Balance, "可用币:", account.Stocks)
Sleep(1000)
}
}
除了使用「策略交互」区域,你还可以在状态栏中动态创建交互按钮:
function main() {
while (true) {
// 创建状态栏按钮
var table = {
type: "table",
title: "操作面板",
cols: ["操作", "动作"],
rows: [
["一键平仓", {"type": "button", "cmd": "closeAll", "name": "执行"}],
["紧急停止", {"type": "button", "cmd": "stop", "name": "停止", "class": "btn btn-xs btn-danger"}]
]
}
LogStatus("`" + JSON.stringify(table) + "`")
var cmd = GetCommand()
if (cmd == "closeAll") {
Log("执行一键平仓")
} else if (cmd == "stop") {
Log("紧急停止")
return // 退出策略
}
Sleep(1000)
}
}
| 分类 | 主要函数 | 用途 |
|---|---|---|
| 行情类 | GetTicker, GetDepth, GetRecords | 获取市场数据 |
| 交易类 | Buy, Sell, CancelOrder, GetOrders | 下单和订单管理 |
| 账户类 | GetAccount, GetPositions | 查询资产和持仓 |
| 工具类 | Log, Sleep, _N, _D | 日志、延时、格式化 |
关于symbol参数:最新版API中,行情类函数(GetTicker、GetDepth、GetRecords)、查询类函数(GetOrders、GetPositions)均支持传入
symbol参数来直接指定要查询的品种,无需提前调用SetContractType()。symbol格式示例:现货"BTC_USDT",永续合约"BTC_USDT.swap"。同时也兼容之前通过SetContractType()设置当前操作品种的方式。此外,平台还提供了exchange.CreateOrder(symbol, side, price, amount)函数,可以直接指定品种和方向下单。
用途:获取当前交易对的最新行情数据,包括最新价、买一价、卖一价等。支持传入symbol参数指定品种。
返回值:Ticker结构
| 字段 | 类型 | 说明 |
|---|---|---|
| Last | number | 最新成交价 |
| Buy | number | 买一价 |
| Sell | number | 卖一价 |
| High | number | 24小时最高价 |
| Low | number | 24小时最低价 |
| Volume | number | 24小时成交量 |
| Time | number | 时间戳(毫秒) |
示例:
function main() {
// 不传参数:使用当前设置的交易对
var ticker = exchange.GetTicker()
if (ticker) {
Log("最新价:", ticker.Last)
Log("买一价:", ticker.Buy)
Log("卖一价:", ticker.Sell)
Log("24H最高:", ticker.High)
Log("24H最低:", ticker.Low)
}
// 传入symbol参数:直接指定品种
var tickerBTC = exchange.GetTicker("BTC_USDT") // 现货
var tickerSwap = exchange.GetTicker("BTC_USDT.swap") // 永续合约
}
用途:获取订单簿数据,即买卖盘挂单情况。
返回值:Depth结构
| 字段 | 类型 | 说明 |
|---|---|---|
| Asks | array | 卖单数组,按价格从低到高排序 |
| Bids | array | 买单数组,按价格从高到低排序 |
| Time | number | 时间戳 |
示例:
function main() {
var depth = exchange.GetDepth()
if (depth) {
Log("卖一价:", depth.Asks[0].Price, "卖一量:", depth.Asks[0].Amount)
Log("买一价:", depth.Bids[0].Price, "买一量:", depth.Bids[0].Amount)
}
}
用途:获取K线数据,用于技术分析和指标计算。
语法:
exchange.GetRecords() // 使用默认周期
exchange.GetRecords(PERIOD_M5) // 指定5分钟周期
exchange.GetRecords("BTC_USDT", 60, 100) // 指定交易对、周期(秒)、数量
常用周期常量:
| 常量 | 周期 |
|---|---|
| PERIOD_M1 | 1分钟 |
| PERIOD_M5 | 5分钟 |
| PERIOD_M15 | 15分钟 |
| PERIOD_M30 | 30分钟 |
| PERIOD_H1 | 1小时 |
| PERIOD_D1 | 1天 |
返回值:Record数组,每个Record包含:
| 字段 | 说明 |
|---|---|
| Time | K线起始时间戳 |
| Open | 开盘价 |
| High | 最高价 |
| Low | 最低价 |
| Close | 收盘价 |
| Volume | 成交量 |
示例:
function main() {
// 获取1小时K线
var records = exchange.GetRecords(PERIOD_H1)
if (records && records.length > 0) {
var lastBar = records[records.length - 1] // 最新一根K线
Log("最新K线 - 开:", lastBar.Open, "高:", lastBar.High,
"低:", lastBar.Low, "收:", lastBar.Close)
Log("K线数量:", records.length)
}
}
用途:下买单(现货买入/合约做多开仓)。
语法:
exchange.Buy(price, amount) // 限价单
exchange.Buy(-1, amount) // 市价单(amount为金额)
参数:
- price:订单价格,-1表示市价单
- amount:订单数量
返回值:成功返回订单ID,失败返回null
示例:
function main() {
// 限价买入:以50000价格买入0.01个
var id = exchange.Buy(50000, 0.01)
Log("限价单ID:", id)
// 市价买入:花100 USDT买入
var id2 = exchange.Buy(-1, 100)
Log("市价单ID:", id2)
}
用途:下卖单(现货卖出/合约做空开仓)。
语法:
exchange.Sell(price, amount) // 限价单
exchange.Sell(-1, amount) // 市价单
示例:
function main() {
// 限价卖出:以60000价格卖出0.01个
var id = exchange.Sell(60000, 0.01)
Log("订单ID:", id)
// 市价卖出:卖出0.01个币
var id2 = exchange.Sell(-1, 0.01)
Log("市价单ID:", id2)
}
用途:直接指定交易品种和方向下单,无需提前调用 SetContractType() 或 SetDirection()。
语法:
exchange.CreateOrder(symbol, side, price, amount)
参数:
- symbol:交易品种,如 "BTC_USDT"(现货)、"BTC_USDT.swap"(永续合约)
- side:交易方向,"buy"(买入/做多)、"sell"(卖出/做空)、"closebuy"(平多)、"closesell"(平空)
- price:订单价格,-1表示市价单
- amount:订单数量
返回值:成功返回订单ID,失败返回null
示例:
function main() {
// 现货买入
var id1 = exchange.CreateOrder("BTC_USDT", "buy", -1, 100) // 市价花100 USDT买入BTC
Log("现货买单ID:", id1)
// 永续合约做多
var id2 = exchange.CreateOrder("BTC_USDT.swap", "buy", 50000, 1) // 限价做多
Log("合约多单ID:", id2)
// 平仓
var id3 = exchange.CreateOrder("BTC_USDT.swap", "closebuy", -1, 1) // 市价平多
Log("平仓单ID:", id3)
}
提示:
CreateOrder是更灵活的下单方式,特别适合同时操作多个品种的策略。Buy()和Sell()函数依然可用,但它们不支持symbol参数,需要配合SetContractType()使用。
用途:取消指定的未完成订单。
语法:
exchange.CancelOrder(orderId)
返回值:true表示撤单请求成功,false表示失败
示例:
function main() {
// 下一个远离当前价格的限价单(不会立即成交)
var id = exchange.Buy(1000, 0.01)
Log("下单成功,订单ID:", id)
Sleep(2000) // 等待2秒
// 撤销订单
var ret = exchange.CancelOrder(id)
Log("撤单结果:", ret ? "成功" : "失败")
}
用途:查询当前未成交的挂单。
返回值:Order数组,无挂单时返回空数组 []
Order结构:
| 字段 | 说明 |
|---|---|
| Id | 订单ID |
| Price | 下单价格 |
| Amount | 下单数量 |
| DealAmount | 已成交数量 |
| Type | 订单类型(0买单/1卖单) |
| Status | 订单状态 |
示例:
function main() {
var orders = exchange.GetOrders()
if (orders.length == 0) {
Log("当前没有未完成的订单")
} else {
Log("未完成订单数量:", orders.length)
for (var i = 0; i < orders.length; i++) {
Log("订单", i+1, "- ID:", orders[i].Id,
"价格:", orders[i].Price, "数量:", orders[i].Amount)
}
}
}
用途:查询当前账户的资产余额。
返回值:Account结构
| 字段 | 说明 |
|---|---|
| Balance | 计价币余额(如USDT) |
| FrozenBalance | 冻结的计价币 |
| Stocks | 交易币余额(如BTC) |
| FrozenStocks | 冻结的交易币 |
示例:
function main() {
var account = exchange.GetAccount()
if (account) {
Log("USDT余额:", account.Balance)
Log("USDT冻结:", account.FrozenBalance)
Log("BTC余额:", account.Stocks)
Log("BTC冻结:", account.FrozenStocks)
}
}
用途:查询合约的持仓信息。
返回值:Position数组
| 字段 | 说明 |
|---|---|
| Symbol | 合约代码 |
| Amount | 持仓数量 |
| Price | 持仓均价 |
| Profit | 未实现盈亏 |
| Type | 持仓方向(0多头/1空头) |
| Margin | 保证金 |
示例:
function main() {
// 方式一:通过symbol参数直接指定品种(推荐)
var positions = exchange.GetPositions("BTC_USDT.swap")
// 方式二:先设置合约类型,再查询(兼容旧写法)
// exchange.SetContractType("swap")
// var positions = exchange.GetPositions()
if (positions.length == 0) {
Log("当前没有持仓")
} else {
for (var i = 0; i < positions.length; i++) {
var pos = positions[i]
Log("持仓:", pos.Symbol,
"方向:", pos.Type == 0 ? "多" : "空",
"数量:", pos.Amount, "盈亏:", pos.Profit)
}
}
}
用途:在日志区域输出信息,支持多参数。
特殊用法:
- 以 @ 结尾:触发消息推送
- 以 #ff0000 等颜色代码:设置文字颜色
function main() {
Log("普通日志")
Log("红色警告", "#ff0000")
Log("推送消息@") // 会推送到你的邮箱/微信等
Log("多个参数:", 123, true, {a: 1})
}
用途:在状态栏显示信息,不写入日志数据库,适合显示实时数据。
function main() {
while (true) {
var ticker = exchange.GetTicker()
// 状态栏实时更新,不会产生大量日志
LogStatus("当前价格:", ticker.Last, "\n更新时间:", _D())
Sleep(1000)
}
}
用途:记录策略收益,并绘制收益曲线。
function main() {
var initBalance = 10000
while (true) {
var account = exchange.GetAccount()
var profit = account.Balance - initBalance
LogProfit(profit) // 记录收益,绘制曲线
Sleep(60000) // 每分钟记录一次
}
}
用途:让程序暂停指定的毫秒数。
Sleep(1000) // 休眠1秒
Sleep(500) // 休眠0.5秒
Sleep(60000) // 休眠1分钟
重要:在轮询循环中必须使用Sleep(),否则会因为请求过于频繁而被交易所限制。
用途:格式化浮点数,控制小数位数。
var num = 3.1415926
Log(_N(num, 2)) // 输出: 3.14
Log(_N(num, 4)) // 输出: 3.1415
// 也可以处理整数部分
Log(_N(1234, -2)) // 输出: 1200
用途:将时间戳转换为可读的日期时间字符串。
Log(_D()) // 输出当前时间,如:2024-01-15 10:30:45
Log(_D(1705285845000)) // 将时间戳转换为日期字符串
FMZ内置了常用的技术指标计算函数(TA库):
| 函数 | 指标名称 |
|---|---|
| TA.MA(records, period) | 移动平均线 |
| TA.EMA(records, period) | 指数移动平均线 |
| TA.MACD(records, fast, slow, signal) | MACD指标 |
| TA.RSI(records, period) | RSI相对强弱指标 |
| TA.KDJ(records, n, k, d) | KDJ随机指标 |
| TA.BOLL(records, period, multiplier) | 布林带 |
示例:计算均线:
function main() {
var records = exchange.GetRecords(PERIOD_H1)
if (records && records.length > 20) {
var ma5 = TA.MA(records, 5) // 5周期均线
var ma20 = TA.MA(records, 20) // 20周期均线
// 获取最新的均线值
var currentMA5 = ma5[ma5.length - 1]
var currentMA20 = ma20[ma20.length - 1]
Log("MA5:", currentMA5, "MA20:", currentMA20)
}
}
这是最简单的策略,功能是定时获取并显示行情数据。
/*
策略名称:行情监控策略
功能说明:每3秒获取一次行情,在状态栏显示关键数据
适合学习:基本结构、API调用、状态栏使用
*/
function main() {
Log("行情监控策略启动")
while (true) {
// 获取行情数据
var ticker = exchange.GetTicker()
// 检查是否获取成功
if (ticker) {
// 构建状态信息
var info = "=== 实时行情 ===" + "\n"
info += "最新价格: " + ticker.Last + "\n"
info += "买一价格: " + ticker.Buy + "\n"
info += "卖一价格: " + ticker.Sell + "\n"
info += "24H最高: " + ticker.High + "\n"
info += "24H最低: " + ticker.Low + "\n"
info += "24H成交量: " + ticker.Volume + "\n"
info += "更新时间: " + _D()
// 更新状态栏
LogStatus(info)
} else {
LogStatus("获取行情失败,等待重试...")
}
// 休眠3秒
Sleep(3000)
}
}
运行效果:策略启动后,状态栏会每3秒更新一次,显示当前的行情数据。
可修改优化: - 修改Sleep的时间调整刷新频率 - 添加更多数据显示,如深度数据 - 添加价格变化提醒
这个策略实现了经典的双均线交叉策略。
/*
策略名称:双均线交叉策略
功能说明:短期均线上穿长期均线时买入,下穿时卖出
适合学习:K线数据、指标计算、交易逻辑、持仓判断
界面参数设置:
- FastPeriod (数字): 5 // 短期均线周期
- SlowPeriod (数字): 20 // 长期均线周期
- TradeAmount (数字): 0.01 // 每次交易数量
*/
// 全局变量:记录上一次的均线状态
var lastCrossState = 0 // 0=未知, 1=金叉, -1=死叉
function main() {
Log("双均线策略启动")
Log("短期均线:", FastPeriod, "长期均线:", SlowPeriod)
while (true) {
// 获取K线数据
var records = exchange.GetRecords(PERIOD_H1)
// 检查K线数量是否足够计算均线
if (!records || records.length < SlowPeriod + 1) {
LogStatus("等待K线数据... 当前:", records ? records.length : 0, "根")
Sleep(1000)
continue
}
// 计算均线
var maFast = TA.MA(records, FastPeriod)
var maSlow = TA.MA(records, SlowPeriod)
// 获取最新和前一个均线值(用于判断交叉)
var currentFast = maFast[maFast.length - 2] // 使用倒数第二个(已完成的K线)
var currentSlow = maSlow[maSlow.length - 2]
var prevFast = maFast[maFast.length - 3]
var prevSlow = maSlow[maSlow.length - 3]
// 判断均线交叉
var crossState = 0
if (prevFast <= prevSlow && currentFast > currentSlow) {
crossState = 1 // 金叉:短期均线上穿长期均线
} else if (prevFast >= prevSlow && currentFast < currentSlow) {
crossState = -1 // 死叉:短期均线下穿长期均线
}
// 获取账户信息
var account = exchange.GetAccount()
var ticker = exchange.GetTicker()
// 交易逻辑
if (crossState == 1 && lastCrossState != 1) {
// 金叉买入
Log("检测到金叉,执行买入")
exchange.Buy(-1, TradeAmount * ticker.Last) // 市价买入
lastCrossState = 1
} else if (crossState == -1 && lastCrossState != -1) {
// 死叉卖出
if (account.Stocks >= TradeAmount) {
Log("检测到死叉,执行卖出")
exchange.Sell(-1, TradeAmount) // 市价卖出
}
lastCrossState = -1
}
// 更新状态栏
var status = "=== 双均线策略 ===" + "\n"
status += "当前价格: " + ticker.Last + "\n"
status += "快线(MA" + FastPeriod + "): " + _N(currentFast, 2) + "\n"
status += "慢线(MA" + SlowPeriod + "): " + _N(currentSlow, 2) + "\n"
status += "信号状态: " + (lastCrossState == 1 ? "金叉(持有)" : lastCrossState == -1 ? "死叉(空仓)" : "等待") + "\n"
status += "账户余额: " + _N(account.Balance, 2) + " USDT" + "\n"
status += "持有数量: " + _N(account.Stocks, 6) + "\n"
status += "更新时间: " + _D()
LogStatus(status)
Sleep(5000) // 5秒检查一次
}
}
核心逻辑说明: 1. 获取K线数据,计算两条均线 2. 判断均线是否发生交叉(比较当前和上一周期的相对位置) 3. 金叉时买入,死叉时卖出 4. 使用lastCrossState避免重复下单
可修改优化: - 调整均线周期参数 - 添加止损止盈逻辑 - 改为合约交易,支持做空
这个策略综合运用了参数、交互、状态栏表格等功能。
/*
策略名称:网格交易策略(演示版)
功能说明:在指定价格范围内设置网格,低买高卖
适合学习:参数系统、交互控件、状态栏表格、完整交易逻辑
界面参数设置:
- BasePrice (数字): 0 // 基准价格,0表示使用当前价
- GridCount (数字): 5 // 单边网格数量
- GridSpacing (数字): 100 // 网格间距(USDT)
- AmountPerGrid (数字): 0.001 // 每格交易量
交互控件设置:
- start: 按钮型, 描述"开始运行"
- stop: 按钮型, 描述"停止运行"
- setBase: 数字型, 描述"设置基准价"
*/
// 全局变量
var isRunning = false
var gridOrders = [] // 存储网格订单信息
function init() {
Log("网格策略初始化")
Log("网格数量:", GridCount, "网格间距:", GridSpacing, "每格数量:", AmountPerGrid)
}
// 计算网格价格
function calculateGridPrices(basePrice) {
var prices = []
for (var i = -GridCount; i <= GridCount; i++) {
if (i != 0) {
prices.push({
price: basePrice + i * GridSpacing,
side: i < 0 ? "buy" : "sell",
level: Math.abs(i)
})
}
}
return prices
}
// 显示状态表格
function showStatus(ticker, account) {
var table = {
type: "table",
title: "网格状态",
cols: ["项目", "数值"],
rows: [
["运行状态", isRunning ? "运行中 #00ff00" : "已停止 #ff0000"],
["当前价格", ticker.Last],
["基准价格", BasePrice > 0 ? BasePrice : "未设置"],
["账户余额", _N(account.Balance, 2) + " USDT"],
["持有数量", _N(account.Stocks, 6)],
["网格数量", GridCount + " x 2"],
["网格间距", GridSpacing + " USDT"],
["更新时间", _D()]
]
}
// 添加控制按钮
var btnStart = {"type": "button", "cmd": "start", "name": "开始"}
var btnStop = {"type": "button", "cmd": "stop", "name": "停止", "class": "btn btn-xs btn-danger"}
var controlTable = {
type: "table",
title: "手动控制",
cols: ["启动", "停止"],
rows: [[btnStart, btnStop]]
}
LogStatus("`" + JSON.stringify([table, controlTable]) + "`")
}
function main() {
while (true) {
// 处理交互命令
var cmd = GetCommand()
if (cmd) {
if (cmd == "start") {
if (BasePrice <= 0) {
var t = exchange.GetTicker()
BasePrice = t.Last
Log("使用当前价格作为基准:", BasePrice)
}
isRunning = true
Log("网格策略开始运行,基准价格:", BasePrice)
} else if (cmd == "stop") {
isRunning = false
Log("网格策略停止运行")
} else if (cmd.indexOf("setBase:") == 0) {
var newBase = parseFloat(cmd.split(":")[1])
if (newBase > 0) {
BasePrice = newBase
Log("基准价格已更新为:", BasePrice)
}
}
}
// 获取行情和账户
var ticker = exchange.GetTicker()
var account = exchange.GetAccount()
if (!ticker || !account) {
Sleep(1000)
continue
}
// 如果策略在运行中,检查网格逻辑
if (isRunning && BasePrice > 0) {
var gridPrices = calculateGridPrices(BasePrice)
// 这里简化处理,实际策略需要更复杂的订单管理
for (var i = 0; i < gridPrices.length; i++) {
var grid = gridPrices[i]
// 检查是否触发网格
if (grid.side == "buy" && ticker.Last <= grid.price) {
// 价格下跌到买入网格
Log("触发买入网格,价格:", grid.price, "层级:", grid.level)
// exchange.Buy(grid.price, AmountPerGrid) // 实际交易时取消注释
} else if (grid.side == "sell" && ticker.Last >= grid.price) {
// 价格上涨到卖出网格
Log("触发卖出网格,价格:", grid.price, "层级:", grid.level)
// exchange.Sell(grid.price, AmountPerGrid) // 实际交易时取消注释
}
}
}
// 更新状态显示
showStatus(ticker, account)
Sleep(2000)
}
}
function onexit() {
Log("策略退出,执行清理...")
// 可以在这里撤销所有未完成订单
}
功能亮点: 1. 参数系统:网格数量、间距、交易量都可在界面配置 2. 交互控件:可以手动启动/停止策略,动态设置基准价 3. 状态栏表格:清晰展示策略运行状态和账户信息 4. 生命周期管理:init()初始化,onexit()清理
可修改优化: - 添加订单跟踪,避免重复下单 - 添加动态网格,跟随价格移动 - 添加总体盈亏统计
FMZ的日志系统是你调试的好帮手:
日志级别:
- Log() - 普通信息
- Log("错误!", "#ff0000") - 红色警告
- 系统自动记录的交易日志
调试技巧:
function main() {
// 打印关键变量的值
var ticker = exchange.GetTicker()
Log("调试 - ticker对象:", JSON.stringify(ticker))
// 在关键位置打印执行到哪里了
Log("执行到第1步")
// ... 一些代码 ...
Log("执行到第2步")
// 打印类型,排查数据问题
var value = "123"
Log("value的类型:", typeof(value), "值:", value)
}
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
null 或 undefined 错误 |
API返回空值 | 加判断:if (ticker) { ... } |
rate limit |
请求频率过高 | 增加Sleep时间 |
insufficient balance |
余额不足 | 检查账户余额,减少交易量 |
invalid order |
订单参数错误 | 检查价格和数量是否合法 |
direction is buy, invalid order type Sell |
合约方向设置错误 | 检查SetDirection是否正确 |
回测系统: - 用于快速验证策略逻辑 - 可以测试不同参数组合 - 注意:回测结果不等于实盘表现,避免过度优化
模拟盘: - 连接交易所的模拟账户 - 真实的市场数据和撮合逻辑 - 建议在模拟盘运行至少1-2周再考虑实盘
恭喜你完成了初级手册的学习!接下来你可以:
当你准备好进行实盘交易时,请记住:
量化交易是一个需要不断学习和实践的领域。希望这份手册能帮助你迈出第一步。记住,耐心和谨慎是成功的关键——先在模拟环境中充分测试,积累经验后再考虑实盘交易。
如果你在学习过程中遇到问题,可以: - 查阅官方文档 - 在社区论坛提问 - 参考策略广场中的示例
祝你量化之路顺利!
本手册基于FMZ量化平台官方文档编写,内容以平台最新文档为准。