avatar of 小草 小草
关注 私信
3
关注
1466
关注者

策略编写初级手册

创建于: 2019-08-13 17:47:27, 更新于: 2026-01-27 17:22:09
comments   33
hits   69901

[TOC]

欢迎来到FMZ量化交易平台!这份手册将带你从零开始,系统学习如何在FMZ平台上编写量化交易策略。无论你是编程新手还是有一定基础的开发者,这份手册都将帮助你快速上手,并最终能够独立编写简单的交易策略。


一、开篇导读

1.1 什么是FMZ量化平台

FMZ(发明者量化)是一个专业的量化交易平台,为用户提供策略编写、回测、模拟盘和实盘交易等一站式服务。平台支持连接全球主流加密货币交易所,让你可以用代码实现自动化交易。

1.2 平台支持的编程语言

FMZ平台支持多种编程语言编写策略:

语言 特点 适合人群
JavaScript 语法简洁、易上手、运行效率高 推荐新手首选
Python 生态丰富、数据分析能力强 有Python基础的用户
C++ 执行速度最快 对性能有极致要求的用户
PINE语言 兼容TradingView Pine Script,轻巧而功能强大 熟悉TradingView的用户

本手册说明:为了让你最快速地入门,本手册以 JavaScript 为例进行讲解。JavaScript 语法简单直观,非常适合初学者。如果你使用其他语言,核心概念和API函数都是相同的,只需参考语法手册中对应语言的示例即可。

1.3 学习路径

理解基本结构 → 熟悉常用API → 编写简单策略 → 回测验证 → 模拟盘运行 → 实盘交易

重要提醒:在你完全熟悉平台和策略之前,请务必先在回测系统和模拟盘中充分测试,不要急于实盘交易!


二、策略编写环境

2.1 在线策略编辑器

FMZ提供了功能完善的在线策略编辑器,无需安装任何软件,打开浏览器即可开始编写策略。

进入编辑器的步骤: 1. 登录 FMZ官网 2. 点击顶部导航栏的「策略库」 3. 点击「新建策略」按钮 4. 选择编程语言(推荐JavaScript) 5. 开始编写你的第一个策略

编辑器主要功能: - 代码高亮和自动补全 - 实时语法检查 - 策略参数配置面板 - 交互控件设置区域 - 一键回测功能

2.2 策略创建与保存

创建策略后,你可以: - 保存策略:点击「保存」按钮,策略会保存在云端 - 版本管理:每次保存都会生成历史版本,方便回溯 - 策略分类:可以为策略添加标签,便于管理

2.3 调试工具

FMZ提供了三种主要的调试方式:

调试方式 说明 使用场景
调试工具 快速测试代码片段,查看API返回值 验证单个函数的行为
回测系统 用历史数据模拟策略运行 验证策略逻辑、评估收益
模拟盘 连接交易所的模拟账户实时运行 验证策略在真实环境中的表现

三、策略基本结构

3.1 main函数:策略的入口

每个FMZ策略都必须有一个 main() 函数,这是策略的入口点。当策略启动时,系统会自动调用这个函数。

function main() {
    // 你的策略代码写在这里
    Log("Hello, FMZ!")
}

3.2 轮询架构:策略的核心模式

量化策略通常需要持续监控市场,因此大多数策略采用「轮询」架构——在一个无限循环中不断获取行情、判断条件、执行交易。

function main() {
    while (true) {                    // 无限循环
        var ticker = exchange.GetTicker()  // 获取行情
        Log("当前价格:", ticker.Last)       // 输出信息
        Sleep(1000)                   // 休眠1秒(1000毫秒)
    }
}

关键点Sleep() 函数非常重要!它控制每次轮询的间隔时间,避免过于频繁地请求交易所接口。建议至少设置1000毫秒(1秒)以上。

3.3 策略生命周期

FMZ策略有完整的生命周期管理,你可以通过特定函数在不同阶段执行代码:

函数 触发时机 用途
init() 策略启动时,main()执行前 初始化变量、设置参数
main() 策略主体 核心交易逻辑
onexit() 策略正常停止时 清理工作、撤销未完成订单
onerror() 策略异常时(仅JS支持) 错误处理

完整的生命周期示例

// 初始化函数(可选)
function init() {
    Log("策略初始化完成")
}

// 主函数(必须)
function main() {
    Log("策略开始运行")
    while (true) {
        // 你的交易逻辑
        Sleep(1000)
    }
}

// 退出清理函数(可选)
function onexit() {
    Log("策略停止,执行清理工作")
}

3.4 全局变量与局部变量

在编写策略时,合理使用变量非常重要:

// 全局变量:在函数外部定义,整个策略都可以访问
var totalProfit = 0
var tradeCount = 0

function main() {
    while (true) {
        // 局部变量:在函数内部定义,只在当前作用域有效
        var ticker = exchange.GetTicker()
        var currentPrice = ticker.Last

        // 全局变量可以在循环中持续累加
        tradeCount++

        LogStatus("交易次数:", tradeCount, "当前价格:", currentPrice)
        Sleep(1000)
    }
}

四、策略参数系统(重点)

4.1 什么是策略参数

「策略参数」是让你的策略变得灵活可配置的关键特性。通过参数化,你可以: - 在不修改代码的情况下调整策略行为 - 方便地进行参数优化和回测对比 - 让策略适应不同的市场环境

没有参数化 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 是界面参数
    // ...
}

4.2 参数类型详解

在策略编辑页面下方的「策略参数」区域,你可以添加不同类型的参数:

类型 说明 代码中的值类型 使用场景
数字型(number) 数值输入框 数字 交易数量、价格、周期等
字符串(string) 文本输入框 字符串 交易对名称、备注等
布尔型(true/false) 开关控件 true/false 功能开关
下拉框(selected) 选项列表 索引值或绑定值 选择模式、方向等
加密串 加密输入框 字符串 API密钥等敏感信息

4.3 参数定义与使用

步骤1:在界面添加参数

在策略编辑器下方的「策略参数」表格中添加一行: - 变量:TradeAmount(这将成为代码中的全局变量名) - 描述:交易数量(显示给用户看的名称) - 类型:数字型(number) - 默认值:0.01

步骤2:在代码中直接使用

// TradeAmount 在界面设置后,自动成为全局变量
function main() {
    Log("设置的交易数量是:", TradeAmount)

    // 直接使用参数进行交易
    var ticker = exchange.GetTicker()
    exchange.Buy(ticker.Sell, TradeAmount)
}

4.4 下拉框参数的使用

下拉框参数特别适合需要在几个选项中选择的场景:

界面设置: - 变量:Direction - 描述:交易方向 - 类型:下拉框(selected) - 默认值:做多|做空|双向(用 | 分隔选项)

代码使用

function main() {
    // Direction 的值是选项的索引:0=做多, 1=做空, 2=双向
    if (Direction == 0) {
        Log("当前设置为做多模式")
    } else if (Direction == 1) {
        Log("当前设置为做空模式")
    } else {
        Log("当前设置为双向模式")
    }
}

4.5 参数分组

当策略参数较多时,可以使用「组件配置」中的「分组」功能,将相关参数归类显示,使界面更加清晰。

4.6 实际示例:可配置的网格交易参数

/*
界面参数设置:
- 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 ? "开启" : "关闭")

    // 根据参数执行策略逻辑...
}

五、策略交互功能(重点)

5.1 什么是策略交互

「策略交互」让你可以在策略运行过程中与之”对话”——通过点击按钮或输入数据来触发特定操作。这对于以下场景非常有用: - 手动触发买入/卖出操作 - 动态修改策略参数 - 紧急情况下的干预操作 - 查询特定信息

5.2 交互控件类型

在策略编辑器的「策略交互」区域,你可以添加以下类型的控件:

控件类型 说明 GetCommand()收到的消息格式
按钮(button) 纯按钮,无输入 控件名
数字型(number) 按钮+数字输入框 控件名:数值
字符串(string) 按钮+文本输入框 控件名:文本
布尔型(boolean) 按钮+开关 控件名:true/false
下拉框(selected) 按钮+下拉选择 控件名:选项索引

5.3 GetCommand()函数详解

GetCommand() 是接收交互命令的核心函数:

function main() {
    while (true) {
        // 获取交互命令
        var cmd = GetCommand()

        // 如果有命令传入
        if (cmd) {
            Log("收到交互命令:", cmd)
        }

        Sleep(1000)
    }
}

返回值说明: - 没有命令时返回空字符串 "" - 有命令时返回格式为 控件名:数据控件名(纯按钮)

5.4 解析交互命令

当交互控件带有输入数据时,需要解析命令:

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)
    }
}

5.5 实际示例:手动交易控制

假设你在「策略交互」中添加了以下控件: - 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)
    }
}

5.6 状态栏中的交互按钮

除了使用「策略交互」区域,你还可以在状态栏中动态创建交互按钮:

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)
    }
}

六、常用API函数详解

6.1 函数分类概览

分类 主要函数 用途
行情类 GetTicker, GetDepth, GetRecords 获取市场数据
交易类 Buy, Sell, CancelOrder, GetOrders 下单和订单管理
账户类 GetAccount, GetPositions 查询资产和持仓
工具类 Log, Sleep, _N, _D 日志、延时、格式化

6.2 行情类函数

关于symbol参数:最新版API中,行情类函数(GetTicker、GetDepth、GetRecords)、查询类函数(GetOrders、GetPositions)均支持传入 symbol 参数来直接指定要查询的品种,无需提前调用 SetContractType()。symbol格式示例:现货 "BTC_USDT",永续合约 "BTC_USDT.swap"。同时也兼容之前通过 SetContractType() 设置当前操作品种的方式。此外,平台还提供了 exchange.CreateOrder(symbol, side, price, amount) 函数,可以直接指定品种和方向下单。

exchange.GetTicker() - 获取实时行情

用途:获取当前交易对的最新行情数据,包括最新价、买一价、卖一价等。支持传入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")    // 永续合约
}

exchange.GetDepth() - 获取深度数据

用途:获取订单簿数据,即买卖盘挂单情况。

返回值: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)
    }
}

exchange.GetRecords() - 获取K线数据

用途:获取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)
    }
}

6.3 交易类函数

exchange.Buy() - 买入下单

用途:下买单(现货买入/合约做多开仓)。

语法

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() - 卖出下单

用途:下卖单(现货卖出/合约做空开仓)。

语法

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)
}

exchange.CreateOrder() - 指定品种下单

用途:直接指定交易品种和方向下单,无需提前调用 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() - 撤销订单

用途:取消指定的未完成订单。

语法

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 ? "成功" : "失败")
}

exchange.GetOrders() - 获取未完成订单

用途:查询当前未成交的挂单。

返回值: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)
        }
    }
}

6.4 账户类函数

exchange.GetAccount() - 获取账户信息

用途:查询当前账户的资产余额。

返回值: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)
    }
}

exchange.GetPositions() - 获取持仓(合约)

用途:查询合约的持仓信息。

返回值: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)
        }
    }
}

6.5 工具类函数

Log() - 输出日志

用途:在日志区域输出信息,支持多参数。

特殊用法: - 以 @ 结尾:触发消息推送 - 以 #ff0000 等颜色代码:设置文字颜色

function main() {
    Log("普通日志")
    Log("红色警告", "#ff0000")
    Log("推送消息@")  // 会推送到你的邮箱/微信等
    Log("多个参数:", 123, true, {a: 1})
}

LogStatus() - 更新状态栏

用途:在状态栏显示信息,不写入日志数据库,适合显示实时数据。

function main() {
    while (true) {
        var ticker = exchange.GetTicker()
        // 状态栏实时更新,不会产生大量日志
        LogStatus("当前价格:", ticker.Last, "\n更新时间:", _D())
        Sleep(1000)
    }
}

LogProfit() - 记录收益

用途:记录策略收益,并绘制收益曲线。

function main() {
    var initBalance = 10000
    while (true) {
        var account = exchange.GetAccount()
        var profit = account.Balance - initBalance
        LogProfit(profit)  // 记录收益,绘制曲线
        Sleep(60000)  // 每分钟记录一次
    }
}

Sleep() - 休眠等待

用途:让程序暂停指定的毫秒数。

Sleep(1000)    // 休眠1秒
Sleep(500)     // 休眠0.5秒
Sleep(60000)   // 休眠1分钟

重要:在轮询循环中必须使用Sleep(),否则会因为请求过于频繁而被交易所限制。


_N() - 数值精度格式化

用途:格式化浮点数,控制小数位数。

var num = 3.1415926
Log(_N(num, 2))   // 输出: 3.14
Log(_N(num, 4))   // 输出: 3.1415

// 也可以处理整数部分
Log(_N(1234, -2)) // 输出: 1200

_D() - 时间格式化

用途:将时间戳转换为可读的日期时间字符串。

Log(_D())                    // 输出当前时间,如:2024-01-15 10:30:45
Log(_D(1705285845000))       // 将时间戳转换为日期字符串

6.6 技术指标函数

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)
    }
}

七、完整策略示例

7.1 示例一:行情监控策略(入门级)

这是最简单的策略,功能是定时获取并显示行情数据。

/*
策略名称:行情监控策略
功能说明:每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的时间调整刷新频率 - 添加更多数据显示,如深度数据 - 添加价格变化提醒


7.2 示例二:简单均线策略(理解交易逻辑)

这个策略实现了经典的双均线交叉策略。

/*
策略名称:双均线交叉策略
功能说明:短期均线上穿长期均线时买入,下穿时卖出
适合学习: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避免重复下单

可修改优化: - 调整均线周期参数 - 添加止损止盈逻辑 - 改为合约交易,支持做空


7.3 示例三:带参数和交互的完整策略(综合应用)

这个策略综合运用了参数、交互、状态栏表格等功能。

/*
策略名称:网格交易策略(演示版)
功能说明:在指定价格范围内设置网格,低买高卖
适合学习:参数系统、交互控件、状态栏表格、完整交易逻辑

界面参数设置:
- 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()清理

可修改优化: - 添加订单跟踪,避免重复下单 - 添加动态网格,跟随价格移动 - 添加总体盈亏统计


八、调试与常见问题

8.1 如何查看日志排查问题

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)
}

8.2 常见报错及解决方法

报错信息 原因 解决方法
nullundefined 错误 API返回空值 加判断:if (ticker) { ... }
rate limit 请求频率过高 增加Sleep时间
insufficient balance 余额不足 检查账户余额,减少交易量
invalid order 订单参数错误 检查价格和数量是否合法
direction is buy, invalid order type Sell 合约方向设置错误 检查SetDirection是否正确

8.3 回测与模拟盘的使用建议

回测系统: - 用于快速验证策略逻辑 - 可以测试不同参数组合 - 注意:回测结果不等于实盘表现,避免过度优化

模拟盘: - 连接交易所的模拟账户 - 真实的市场数据和撮合逻辑 - 建议在模拟盘运行至少1-2周再考虑实盘

8.4 新手常犯的错误

  1. 忘记Sleep:导致请求过于频繁,被交易所限制
  2. 不检查返回值:API可能返回null,直接使用会报错
  3. 市价单金额/数量混淆:现货市价买单的amount是金额,卖单是数量
  4. 合约方向未设置:使用Buy/Sell下合约单时必须先SetContractType和SetDirection,推荐使用CreateOrder直接指定品种和方向
  5. 回测过度优化:参数调得太完美,实盘效果差
  6. 急于实盘:没有充分测试就上实盘,造成损失

九、进阶指引

9.1 后续学习路径

恭喜你完成了初级手册的学习!接下来你可以:

  1. 深入学习API:阅读完整的语法手册,了解更多函数
  2. 学习模板库:FMZ提供了很多实用的模板函数
  3. 研究策略广场:学习其他用户分享的策略思路
  4. 尝试合约交易:学习合约交易的特殊API
  5. 探索高级功能:多交易所、多线程、数据库等

9.2 推荐资源

9.3 从模拟到实盘的注意事项

当你准备好进行实盘交易时,请记住:

  1. 小资金起步:先用小资金测试,确认没问题再增加
  2. 设置风控:一定要有止损逻辑,防止意外损失
  3. 监控运行:定期检查策略运行状态
  4. 保持学习:市场在变化,策略也需要不断优化
  5. 分散风险:不要把所有资金放在一个策略上

结语

量化交易是一个需要不断学习和实践的领域。希望这份手册能帮助你迈出第一步。记住,耐心和谨慎是成功的关键——先在模拟环境中充分测试,积累经验后再考虑实盘交易。

如果你在学习过程中遇到问题,可以: - 查阅官方文档 - 在社区论坛提问 - 参考策略广场中的示例

祝你量化之路顺利!


本手册基于FMZ量化平台官方文档编写,内容以平台最新文档为准。

相关推荐
全部留言
avatar of gaoencheer
gaoencheer
api
2023-05-03 01:18:50
avatar of Science
Science
如何在本地实现策略运行呢?我写了一个简单的Log输出语句,并且按照文末的操作。 第一步,先用一台笔记本作为服务器,运行托管者程序; 第二步,写一个简单的Log输出信息的test.py程序(FMZ 的API接口函数); 第三步,按文末那样,写个runfile,通过run.py调用test.py运行。 /upload/asset/1add39483ef82d45b3ce3.png
2022-08-14 19:10:55
avatar of gyp9
gyp9
我买的网易云量化交易课程怎么没了,现在去哪里看
2021-12-15 22:59:21
avatar of gyp9
gyp9
谢谢
2021-12-16 11:07:11
avatar of 小草
小草
一直在网易 https://study.163.com/course/courseMain.htm?share=2&shareId=400000000602076&courseId=1006074239&_trace_c_p_k2_=c3f5d238efc3457d93c8b92c0398d2b2
2021-12-16 09:58:01
avatar of MonuRajak
MonuRajak
many
2021-11-21 19:21:46
avatar of MonuRajak
MonuRajak
hi
2021-11-21 19:21:34
avatar of 伯仲
伯仲
学习ing
2021-05-12 11:35:05
avatar of wqy
wqy
有一个小的文字错误,GetAccount 获取账户 介绍中,FrozenStocks应该是冻结余额而不是可用余额吧
2021-04-29 11:13:06
avatar of dxz3commas
dxz3commas
加我进群,我的实盘执行不起来
2025-04-27 23:41:51
avatar of dxz3commas
dxz3commas
有没有做BTB的实盘教程,
2025-04-27 23:40:54
avatar of 小草
小草
加首页微信,拉你入群
2021-04-29 18:39:54
avatar of wqy
wqy
大佬麻烦问下咱们有官方交流群吗?有时候遇到问题不知道该在哪提问
2021-04-29 16:39:28
avatar of 小草
小草
改了
2021-04-29 11:34:45
avatar of 谭雅少尉
谭雅少尉
getorder outtime 获取订单超时,okex的交易所,怎么办
2021-04-08 17:07:52
avatar of 小草
小草
再次获取
2021-04-22 09:42:55
avatar of 乌木十二造高招
乌木十二造高招
担保资产率获取不到吗,到0%会被强制平仓的担保资产率
2021-01-10 17:51:31
avatar of 小草
小草
原始信息里有,可以用GetRawJSON或者查看字段里的info信息
2021-01-11 08:47:43
avatar of shifeng2020
shifeng2020
我是看1分钟k线图操作的,所以Python死循环的sleep time 可以设置为0.1s,也就是sleep(100)吗,我看你其中写过一个sleep(10),也就是0.1s不会超过huobi HM的API限制吗?
2020-12-02 17:48:08
avatar of 东风化宇
东风化宇
exchange.SetDirection("closebuy"); //如果是永续合约,直接设置exchange.SetDirection("sell") 这儿我试了OKex的永续合约,如果设置成 sell,直接开空了,平不是平多
2020-06-04 08:48:46
avatar of 东风化宇
东风化宇
exchange.SetDirection("closebuy"); //如果是永续合约,直接设置exchange.SetDirection("sell") 这儿我试了OKex的永续合约,如果设置成 sell,直接开空了,平不是平多
2020-06-04 08:48:45
avatar of 东风化宇
东风化宇
不错不错,还有管理回复。。我发现代码里好多拼写错误,哈哈
2020-06-04 11:52:33
avatar of 小草
小草
有些永续合约允许双向持仓的,需要设置平仓。我更新一下,原来只有bitmex
2020-06-04 08:59:49
avatar of 东风化宇
东风化宇
GetOrders 的代码里面有两个拼写错误。。。一个是 function写成了 fuction,另一个是for循环的条件里 ; 打成了 ,
2020-06-04 08:14:49
avatar of 小草
小草
嗯嗯,已改正,感谢指出错误
2020-06-04 09:06:37
avatar of 东风化宇
东风化宇
是我错了。。。 exchange.Buy(-1, 0.5),交易对是ETH_BTC,市价单代表买入0.5BTC的ETH exchange.Buy(price, 0.5),如果是这种限价单,则代表用price的价格买入 0.5ETH
2020-06-04 07:54:31
avatar of 东风化宇
东风化宇
exchange.Buy(-1, 0.5),交易对是ETH_BTC,则代表市价买入0.5BTC的ETH 这里应该是【代表市价买入0.5ETH】
2020-06-04 07:41:31