avatar of 发明者量化-小小梦 发明者量化-小小梦
关注 私信
4
关注
1137
关注者

说走就走的量化旅程,从 FMZ 开始

创建于: 2025-04-18 09:31:42, 更新于: 2025-04-26 11:50:01
comments   0
hits   327

说走就走的量化旅程,从 FMZ 开始

引言

有没有想过,不需要通宵写代码搭框架、不用自己设计UI、各种设计细节和机制,量化交易也可以轻松入门、即刻启程?在 FMZ 量化平台上,一切变得可能。你无需高深的编程背景,也不用担心复杂的部署流程——只要一台电脑,一个账户,就能开始你的“说走就走”的量化旅程。本文将带你从 0 开始,快速上手 FMZ,感受自动化交易的魅力,用数据和策略掌握市场节奏。不论你是初学者,还是寻求效率提升的老手,这次的旅程都值得一试。

有任何不懂的地方,别担心!可以在 FMZ 社区发帖,或者直接留言告诉我,一起把策略跑通不是梦!

  • FMZ平台上@littleDream
  • FMZ平台工单
  • 社区发帖

量化交易入门者的困惑

经常和平台初学者沟通、聊天,量化交易初学者通常困惑于完整的一个设计流程。对于自己有交易想法常常无从下手、不知所措。

困惑于:

  • 如何设计开仓、平仓
  • 如何设计收益计算
  • 如何设计策略重启继续交易进度
  • 如何设计策略图表显示
  • 如何设计策略交互控制

我们来一起解决以上困惑。

设计讲解

在量化交易的世界里,策略设计往往是一场没有终点的探索之旅。你可能试过写指标,也试过盲目跟风买卖信号,但真正能走得远的,是那些能“看得见、调得动、稳得住”的策略系统。基于 FMZ 量化平台来一场“说走就走”的实战体验。构建一个简单策略,从参数设置、图表展示,到交互功能与盈亏计算,完整打通一个策略的设计需求。

策略思路是一个 基于 ATR 的逐级加仓策略,逐级网格建仓逻辑(多空双向),ATR 自适应波动计算,清仓逻辑(当行情反转至中轴)。

本策略基于以下设计需求:

根据价格突破不同等级进行加仓、平仓

设置两个数组用来控制逐级加仓。

var arrUp = null 
var arrDown = null 

每次加仓之后就把仓位信息push进数组,方便控制仓位、也方便策略实盘界面上数据显示。

根据价格突破等级,开仓、平仓;开仓平仓为了简便设计,均使用市价单,简单有效。

            if (close > up && i >= arrUp.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "sell", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue
                }
                arrUp.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrUp", arrUp)
                arrSignal.push([r[r.length - 1].Time, "short", close, tradeAmount])
                Log([r[r.length - 1].Time, "short", close, tradeAmount], "@")
            } else if (close < down && i >= arrDown.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "buy", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue 
                }
                arrDown.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrDown", arrDown)
                arrSignal.push([r[r.length - 1].Time, "long", close, tradeAmount])
                Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")
            } else if (((arrUp.length > 0 && close < mid) || (arrDown.length > 0 && close > mid)) && !isPaused) {
                clear(pos, r)
            }

清仓,使用一个函数处理。在每次清仓时需要重置一些数据结构,所以需要把清仓功能封装为一个函数,以便交互模块中复用。

function clear(positions, r) {
    var close = r[r.length - 1].Close
    for (var p of positions) {
        if (p.Type == PD_LONG) {
            var id = exchange.CreateOrder(symbol, "closebuy", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closelong", close, p.Amount])
            Log([r[r.length - 1].Time, "closelong", close, p.Amount], "@")
        } else if (p.Type == PD_SHORT) {
            var id = exchange.CreateOrder(symbol, "closesell", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closeshort", close, p.Amount])
            Log([r[r.length - 1].Time, "closeshort", close, p.Amount], "@")
        }
    }
    arrUp = []
    arrDown = []
    _G("arrUp", arrUp)
    _G("arrDown", arrDown)
    var profit = calcProfit()
    LogProfit(profit)
}

仓位逐级分配

分多个层级,最大层级为:maxRatio。每个层级计算不同的价格阈值。

        for (var i = 0; i < maxRatio; i++) {                        
            var up = open + atr[atr.length - 1] * (i + 1)
            var mid = open
            var down = open - atr[atr.length - 1] * (i + 1)
            atrs.push([open, (i + 1), atr])
            
            var tradeAmount = baseAmount * Math.pow(2, i)
            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }
        // ...
        }

支持动态参数调整、暂停运行、快速清仓等交互

设计交互功能、清仓、暂停、取消暂停、修改参数等。在FMZ上设计交互是很便捷的,平台提供了很多交互控件。我们只需要把交互控件添加到策略上,然后在策略代码中写好接收到消息时的各种识别、处理代码就可以了。

        var cmd = GetCommand()
        if (cmd) {
            Log("交互指令:", cmd)
            var arrCmd = cmd.split(":")
            if (arrCmd.length == 2) {
                var strCmd = arrCmd[0]
                var param = parseFloat(arrCmd[1])
                if (strCmd == "atrPeriod") {
                    atrPeriod = param
                    Log("修改ATR参数:", atrPeriod)
                }
            } else {
                if (cmd == "isPaused" && !isPaused) {
                    isPaused = true
                    Log("暂停交易")
                } else if (cmd == "isPaused" && isPaused) {
                    isPaused = false 
                    Log("取消暂停交易")
                } else if (cmd == "clearAndPaused") {
                    clear(pos, r)
                    isPaused = true
                    Log("清仓、暂停交易")
                }
            }
        }

具有开仓/平仓提醒机制

在策略开仓、平仓时,在FMZ上可以很方便的把消息推送到邮箱、FMZ APP、第三方接口等。

Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")  // 消息推送

收到消息推送(FMZ APP等也会同步收到推送):

说走就走的量化旅程,从 FMZ 开始

实时统计并展示收益与持仓

计算盈亏的函数,在每次平仓时调用,计算盈亏并且输出盈亏曲线。

function calcProfit() {
    var initAcc = _G("initAcc")
    var nowAcc = _C(exchange.GetAccount)
    var profit = nowAcc.Equity - initAcc.Equity
    return profit
}

支持状态持久化(断点恢复)

使用FMZ上的_G()函数,很轻松的可以设计出策略进度恢复机制。

    if (isReset) {
        _G(null)
        LogProfitReset()
        LogReset(1)
        c.reset()
    }

    arrUp = _G("arrUp")
    if (!arrUp) {
        arrUp = []
        _G("arrUp", arrUp)
    }

    arrDown = _G("arrDown")
    if (!arrDown) {
        arrDown = []
        _G("arrDown", arrDown)
    }

按金额下单的设计

合约交易时,下单接口的下单量都是合约张数,所以经常有用户需求,如何以U的数量下单:

            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

其实很简单,用金额除以价格就可以了。

预留比例设计

如果希望账户总是预留一定资金作为风险控制,可以设计这种简单的机制。

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }

可视化图表

在跑实盘时,肯定是需要对策略观察的,需要观察账户权益、策略状态、策略持仓、订单信息、行情图表等,这些一并设计如下:

        if (isShowPlot) {
            r.forEach(function(bar, index) {
                c.begin(bar)
                for (var i in atrs) {
                    var arr = atrs[i]
                    var up = arr[0] + arr[2][index] * arr[1]
                    var mid = arr[0]
                    var down = arr[0] - arr[2][index] * arr[1]
                    c.plot(up, 'up_' + (i + 1))
                    c.plot(mid, 'mid_' + (i + 1))
                    c.plot(down, 'down_' + (i + 1))
                }

                for (var signal of arrSignal) {
                    if (signal[0] == bar.Time) {
                        c.signal(signal[1], signal[2], signal[3])
                    }
                }

                c.close()
            })
        }

        // ...

        var orderTbl = {"type": "table", "title": "order", "cols": ["symbol", "type", "ratio", "price", "amount"], "rows": []}
        for (var i = arrUp.length - 1; i >= 0; i--) {
            var order = arrUp[i]
            orderTbl["rows"].push([order["symbol"], "short", order["ratio"], order["price"], order["amount"]])
        }
        for (var i = 0; i < arrDown.length; i++) {
            var order = arrDown[i]
            orderTbl["rows"].push([order["symbol"], "long", order["ratio"], order["price"], order["amount"]])
        }

        var posTbl = {"type": "table", "title": "pos", "cols": ["symbol", "type", "price", "amount"], "rows": []}
        for (var i = 0; i < pos.length; i++) {
            var p = pos[i]
            posTbl["rows"].push([p.Symbol, p.Type == PD_LONG ? "long" : "short", p.Price, p.Amount])
        }

        LogStatus(_D(), "初始权益:" + initAcc.Equity, ", 当前权益:" + acc.Equity, ", 运行状态:" + (isPaused ? "暂停交易" : "运行中"), 
            "\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")

最终200+行代码实现了一个可以回测、可以实盘的完整策略。实现了我们的最终目标:在FMZ上打造一个“可视化 + 交互 + 自动化”等,多位一体的量化交易系统。

策略运行效果与回测结果

回测仅供参考、做量化交易的都知道「回测」是不可能100%模拟实际场景,回测更多的作用是检验策略逻辑、检验策略健壮性、基本功能测试等。

说走就走的量化旅程,从 FMZ 开始

说走就走的量化旅程,从 FMZ 开始

策略代码、参数设计

参数设计:

说走就走的量化旅程,从 FMZ 开始

交互设计:

说走就走的量化旅程,从 FMZ 开始

策略源码:

/*backtest
start: 2024-04-27 18:40:00
end: 2025-04-10 00:00:00
period: 15m
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT","balance":100}]
*/

var atrPeriod = 20
var arrUp = null 
var arrDown = null 
var arrSignal = []

function calcProfit() {
    var initAcc = _G("initAcc")
    var nowAcc = _C(exchange.GetAccount)
    var profit = nowAcc.Equity - initAcc.Equity
    return profit
}

function clear(positions, r) {
    var close = r[r.length - 1].Close
    for (var p of positions) {
        if (p.Type == PD_LONG) {
            var id = exchange.CreateOrder(symbol, "closebuy", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closelong", close, p.Amount])
            Log([r[r.length - 1].Time, "closelong", close, p.Amount], "@")
        } else if (p.Type == PD_SHORT) {
            var id = exchange.CreateOrder(symbol, "closesell", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closeshort", close, p.Amount])
            Log([r[r.length - 1].Time, "closeshort", close, p.Amount], "@")
        }
    }
    arrUp = []
    arrDown = []
    _G("arrUp", arrUp)
    _G("arrDown", arrDown)
    var profit = calcProfit()
    LogProfit(profit)
}

function main() {
    var symbolInfo = symbol.split(".")
    if (symbolInfo.length != 2) {
        throw "error symbol:" + symbol
    } else {
        exchange.SetCurrency(symbolInfo[0])
        exchange.SetContractType(symbolInfo[1])
    }

    exchange.SetPrecision(pricePrecision, amountPrecision)

    let c = KLineChart({
        overlay: true
    }) 

    if (isReset) {
        _G(null)
        LogProfitReset()
        LogReset(1)
        c.reset()
    }

    arrUp = _G("arrUp")
    if (!arrUp) {
        arrUp = []
        _G("arrUp", arrUp)
    }

    arrDown = _G("arrDown")
    if (!arrDown) {
        arrDown = []
        _G("arrDown", arrDown)
    }

    var initAcc = _G("initAcc")
    if (!initAcc) {
        initAcc = _C(exchange.GetAccount)
        _G("initAcc", initAcc)
    }

    var isPaused = false     
    while (true) {
        var atrs = []        
        var r = _C(exchange.GetRecords, symbol)
        var pos = _C(exchange.GetPositions, symbol)
        var acc = _C(exchange.GetAccount)
        var open = r[r.length - 1].Open
        var close = r[r.length - 1].Close
        var atr = TA.ATR(r, atrPeriod)
        
        for (var i = 0; i < maxRatio; i++) {                        
            var up = open + atr[atr.length - 1] * (i + 1)
            var mid = open
            var down = open - atr[atr.length - 1] * (i + 1)
            atrs.push([open, (i + 1), atr])
            
            var tradeAmount = baseAmount * Math.pow(2, i)
            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }

            if (close > up && i >= arrUp.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "sell", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue
                }
                arrUp.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrUp", arrUp)
                arrSignal.push([r[r.length - 1].Time, "short", close, tradeAmount])
                Log([r[r.length - 1].Time, "short", close, tradeAmount], "@")
            } else if (close < down && i >= arrDown.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "buy", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue 
                }
                arrDown.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrDown", arrDown)
                arrSignal.push([r[r.length - 1].Time, "long", close, tradeAmount])
                Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")
            } else if (((arrUp.length > 0 && close < mid) || (arrDown.length > 0 && close > mid)) && !isPaused) {
                clear(pos, r)
            }
        }

        if (isShowPlot) {
            r.forEach(function(bar, index) {
                c.begin(bar)
                for (var i in atrs) {
                    var arr = atrs[i]
                    var up = arr[0] + arr[2][index] * arr[1]
                    var mid = arr[0]
                    var down = arr[0] - arr[2][index] * arr[1]
                    c.plot(up, 'up_' + (i + 1))
                    c.plot(mid, 'mid_' + (i + 1))
                    c.plot(down, 'down_' + (i + 1))
                }

                for (var signal of arrSignal) {
                    if (signal[0] == bar.Time) {
                        c.signal(signal[1], signal[2], signal[3])
                    }
                }

                c.close()
            })
        }

        var cmd = GetCommand()
        if (cmd) {
            Log("交互指令:", cmd)
            var arrCmd = cmd.split(":")
            if (arrCmd.length == 2) {
                var strCmd = arrCmd[0]
                var param = parseFloat(arrCmd[1])
                if (strCmd == "atrPeriod") {
                    atrPeriod = param
                    Log("修改ATR参数:", atrPeriod)
                }
            } else {
                if (cmd == "isPaused" && !isPaused) {
                    isPaused = true
                    Log("暂停交易")
                } else if (cmd == "isPaused" && isPaused) {
                    isPaused = false 
                    Log("取消暂停交易")
                } else if (cmd == "clearAndPaused") {
                    clear(pos, r)
                    isPaused = true
                    Log("清仓、暂停交易")
                }
            }
        }

        var orderTbl = {"type": "table", "title": "order", "cols": ["symbol", "type", "ratio", "price", "amount"], "rows": []}
        for (var i = arrUp.length - 1; i >= 0; i--) {
            var order = arrUp[i]
            orderTbl["rows"].push([order["symbol"], "short", order["ratio"], order["price"], order["amount"]])
        }
        for (var i = 0; i < arrDown.length; i++) {
            var order = arrDown[i]
            orderTbl["rows"].push([order["symbol"], "long", order["ratio"], order["price"], order["amount"]])
        }

        var posTbl = {"type": "table", "title": "pos", "cols": ["symbol", "type", "price", "amount"], "rows": []}
        for (var i = 0; i < pos.length; i++) {
            var p = pos[i]
            posTbl["rows"].push([p.Symbol, p.Type == PD_LONG ? "long" : "short", p.Price, p.Amount])
        }

        LogStatus(_D(), "初始权益:" + initAcc.Equity, ", 当前权益:" + acc.Equity, ", 运行状态:" + (isPaused ? "暂停交易" : "运行中"), 
            "\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")
        Sleep(5000)
    }
}

策略仅仅为教学使用,虽然可以实盘,而且目前实盘有盈利,但是长久效果如何还是需要时间检验。策略画图部分还有优化空间,可以避免一些重复操作提升程序效率,策略逻辑方面也可以进一步优化。

实盘是一场远行

说走就走的量化旅程,从 FMZ 开始

来自 GPT 充满诗意的总结:

实盘是一场远行,不问归期,只求心安。每一次开仓,都是在茫茫市场中撒下希望的灯火;每一次止损,都是在风雨中学会更坚定地前行。行情如潮,盈亏如梦,我们在数字的浪尖起舞,也在策略的灯塔下守望。愿你我都能在这场远行中,既不迷失方向,也不畏惧孤独,最终抵达属于自己的那片盈光。

总结:从策略开发到系统思维

本文不仅介绍了一个完整策略,更重要的是一种“系统化”的策略开发思路。从策略设计、状态管理、风险控制、图表交互、再到实战落地,这是一套能被反复复用的模板,也是量化交易走向专业化的必经之路。

希望你能借助 FMZ 平台,打造属于自己的自动化交易体系,让每一次信号都不再错过。

感谢您的阅读与支持,策略仅为教学使用,实盘请慎用。

相关推荐