随着人工智能(AI)技术飞速发展,已经在多个领域展现了极高的效率和创造力。量化交易作为一个高度技术化的领域,自然也在积极探索AI的应用。然而,实践中我们会发现,单纯依赖AI直接生成完整、稳定且可持续盈利的交易策略,仍然存在较大的挑战。
特别是对于平台上的新加入用户来说,由于编程技术薄弱,很难把自己的交易思路实现成策略代码。虽然目前可以把思路告诉AI,让AI输出策略。但是实施效果却不太符合预期。我也经常会遇到用户拿着AI生成的代码来咨询问题,对于AI生成的策略有时候一眼就能看出来。因为目前阶段AI生成的策略代码还有很多问题,对于AI的这样使用不仅没有解决任何问题,还给新手带来了更多的困惑、问题。可谓越学习越迷茫,最后就「从入门到放弃了」。
小编个人认为目前AI直接产出策略的问题,有主要的2个原因:
- 每个人对于策略需求描述的逻辑性、准确性、流程清晰程度等差别很大,AI大模型对于这些自然语言的理解就会有偏差。
- AI大模型对于平台的功能、API等理解程度有限,因为通用的模型并没有针对平台的信息进行训练,产生的信息准确度有限。
那么,是否有其他更高效的应用方式?本文想分享一种新的思考方向:让AI辅助我们学习已有策略,理解策略设计,提取关键细节与技巧,并进一步分析其有效性与改进空间。 这种方法不仅能帮助我们更快掌握策略设计的精髓,还能系统提升自己的量化交易水平。
发挥AI的优势,AI对于具体的代码分析理解程度是比较准确的,因为代码数据对于AI来说「1就是1,2就是2」不会有逻辑混乱、歧义、等自然语言描述需求时带来的问题。所以何不使用AI的优势来降低人工的工作量,充分发挥人工的优势。
如何通过AI反向学习策略?
具体可以分为以下几个步骤:
-
选择一个已有的策略
可以是自己编写的、开源的,或是发明者量化平台上的优秀策略样本。 -
让AI帮助我们讲解该策略
理解整体思路
梳理每一部分的功能模块
明确使用的指标、参数、交易逻辑
-
进一步提问,引导AI分析策略的优劣
策略在什么行情下表现较好?
可能的风险点有哪些?
有哪些可以优化和改进的地方?
-
结合自己的思考进行验证与调整
在不同品种、不同周期上回测
加入额外的过滤条件或风险控制措施
观察性能变化,形成自己的见解
实践一下
我们来让AI学习一个策略,并且给我们讲解一下,看是否符合预期。可以帮助我们学习量化。
策略样本
基于EMA趋势过滤的逐级加仓交易策略设计与实现
策略地址:https://www.fmz.com/strategy/492116
javascript
/*backtest
start: 2024-10-01 00:00:00
end: 2025-04-23 00:00:00
period: 1h
basePeriod: 1m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
args: [["targetProfit",20],["amount",20],["amountPrecision",3],["isAmountForUSDT",true]]
*/
function getTotalEquity_OKEX_V5() {
var totalEquity = null
var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT")
if (ret) {
try {
totalEquity = parseFloat(ret.data[0].details[0].eq)
} catch(e) {
Log("获取账户总权益失败!")
return null
}
}
return totalEquity
}
function getTotalEquity_Binance() {
var totalEquity = null
var ret = exchange.GetAccount()
if (ret) {
try {
totalEquity = parseFloat(ret.Info.totalWalletBalance)
} catch(e) {
Log("获取账户总权益失败!")
return null
}
}
return totalEquity
}
function getTotalEquity() {
var exName = exchange.GetName()
if (exName == "Futures_OKCoin") {
return getTotalEquity_OKEX_V5()
} else if (exName == "Futures_Binance") {
return getTotalEquity_Binance()
} else {
throw "不支持该交易所"
}
}
function ceilToDecimals(value, decimals) {
const factor = Math.pow(10, decimals);
return Math.ceil(value * factor) / factor;
}
function cancelAll() {
while (1) {
var orders = _C(exchange.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
exchange.CancelOrder(orders[i].Id, orders[i])
Sleep(interval)
}
Sleep(interval)
}
}
function trade(distance, price, amount) {
var tradeFunc = null
if (distance == "buy") {
tradeFunc = exchange.Buy
} else if (distance == "sell") {
tradeFunc = exchange.Sell
} else if (distance == "closebuy") {
tradeFunc = exchange.Sell
} else {
tradeFunc = exchange.Buy
}
exchange.SetDirection(distance)
return tradeFunc(price, amount)
}
function openLong(price, amount) {
return trade("buy", price, amount)
}
function openShort(price, amount) {
return trade("sell", price, amount)
}
function coverLong(price, amount) {
return trade("closebuy", price, amount)
}
function coverShort(price, amount) {
return trade("closesell", price, amount)
}
function plotRecords(c, buyOrder, sellOrder, pos) {
var bars = _C(exchange.GetRecords)
if (bars.length == 0) {
return
}
bars.forEach(function(bar, index) {
c.begin(bar)
if (index == bars.length - 1) {
if (buyOrder) {
c.hline(buyOrder.Price, "buy", "rgba(255, 0, 0, 0.2)", "dotted")
}
if (sellOrder) {
c.hline(sellOrder.Price, "sell", "rgba(0, 255, 0, 0.2)", "dotted")
}
if (pos && pos.length == 1) {
c.hline(pos[0].Price, "pos", "rgba(0, 0, 255, 0.2)", "dashed")
}
}
c.close()
})
}
var buyOrderId = null
var sellOrderId = null
var logStatusMsgBuff = ""
function main() {
var exName = exchange.GetName()
if (isSimulate && exName == "Futures_OKCoin") {
exchange.IO("simulate", true)
}
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
exchange.SetContractType(contractType)
exchange.SetPrecision(pricePrecision, amountPrecision)
Log("设置精度", pricePrecision, amountPrecision)
exchange.SetMarginLevel(marginLevel)
if (totalEq == -1 && !IsVirtual()) {
var recoverTotalEq = _G("totalEq")
if (!recoverTotalEq) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
totalEq = currTotalEq
_G("totalEq", currTotalEq)
} else {
throw "获取初始权益失败"
}
} else {
totalEq = recoverTotalEq
}
}
var addCounter = _G("addCounter")
if (!addCounter) {
addCounter = 1
if (setAddCounter != -1) {
addCounter = setAddCounter
}
_G("addCounter", addCounter)
} else {
addCounter -= 1
}
let c = KLineChart({
overlay: true
})
var isLock = false
while (true) {
var ticker = _C(exchange.GetTicker)
var pos = _C(exchange.GetPosition)
if (pos.length > 1) {
Log(pos)
throw "同时有多空持仓"
}
var r = _C(exchange.GetRecords, 60 * 60)
var ema = TA.EMA(r, 60)
if (Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] > 0.03) {
cancelAll()
isLock = true
}
if (Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] < 0.02) {
isLock = false
}
if (isLock) {
LogStatus(_D(), "暂停, 检测阈值:", _N(Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2], 3), logStatusMsgBuff)
plotRecords(c, null, null, pos)
Sleep(interval)
continue
}
var currentAcc = _C(exchange.GetAccount)
if (currentAcc.Balance < totalEq * reserve) {
throw "no money, stop"
}
if (addCounter > maxAddCounter) {
LogStatus(_D(), "加仓已达到上限", logStatusMsgBuff)
if (isMaxAddCounterClear && pos.length >= 1) {
Log("加仓已达到上限,撤单,清仓")
cancelAll()
if (pos[0].Type == PD_LONG) {
var coverId = coverLong(-1, pos[0].Amount)
} else if (pos[0].Type == PD_SHORT) {
var coverId = coverShort(-1, pos[0].Amount)
}
addCounter = 1
}
continue
}
if (pos.length == 0) {
if (!IsVirtual()) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
LogProfit(currTotalEq - totalEq, "当前总权益:", currTotalEq)
}
}
var tradeAmountLong = amount
var tradeAmountShort = amount
if (isAmountForUSDT) {
tradeAmountLong = ceilToDecimals(tradeAmountLong * 1.01 / (ticker.Last - targetProfit / 5) / oneCtValue, amountPrecision)
tradeAmountShort = ceilToDecimals(tradeAmountShort * 1.01 / (ticker.Last + targetProfit / 5) / oneCtValue, amountPrecision)
}
buyOrderId = openLong(ticker.Last - targetProfit / 5, tradeAmountLong)
sellOrderId = openShort(ticker.Last + targetProfit / 5, tradeAmountShort)
addCounter = 1
_G("addCounter", addCounter)
} else if (pos[0].Type == PD_LONG) {
var n = ratio
var price = ticker.Last
var addAmount = isDoubling ? pos[0].Amount : (isAmountForUSDT ? (ceilToDecimals(amount * 1.01 / (price - targetProfit * n) / oneCtValue, amountPrecision)) : amount)
buyOrderId = openLong(price - targetProfit * n, addAmount)
sellOrderId = coverLong(pos[0].Price + targetProfit, pos[0].Amount)
addCounter++
_G("addCounter", addCounter)
} else if (pos[0].Type == PD_SHORT) {
var n = ratio
var price = ticker.Last
var addAmount = isDoubling ? pos[0].Amount : (isAmountForUSDT ? (ceilToDecimals(amount * 1.01 / (price + targetProfit * n) / oneCtValue, amountPrecision)) : amount)
buyOrderId = coverShort(pos[0].Price - targetProfit, pos[0].Amount)
sellOrderId = openShort(price + targetProfit * n, addAmount)
addCounter++
_G("addCounter", addCounter)
}
if (!sellOrderId || !buyOrderId) {
cancelAll()
buyOrderId = null
sellOrderId = null
continue
}
while (1) {
var isFindBuyId = false
var isFindSellId = false
var orders = _C(exchange.GetOrders)
var buyOrder = null
var sellOrder = null
for (var i = 0 ; i < orders.length ; i++) {
if (buyOrderId == orders[i].Id) {
isFindBuyId = true
buyOrder = orders[i]
}
if (sellOrderId == orders[i].Id) {
isFindSellId = true
sellOrder = orders[i]
}
}
if (!isFindSellId && !isFindBuyId) {
cancelAll()
break
} else if (!isFindBuyId) {
Log("买单成交")
cancelAll()
break
} else if (!isFindSellId) {
Log("卖单成交")
cancelAll()
break
}
var acc = _C(exchange.GetAccount)
var tbl = {"type": "table", "title": "data", "cols": ["data", "symbol", "type", "price", "amount"], "rows": []}
if (buyOrder) {
tbl.rows.push(["订单", buyOrder.Symbol, buyOrder.Type == ORDER_TYPE_BUY ? "买入" : "卖出", buyOrder.Price, buyOrder.Amount])
}
if (sellOrder) {
tbl.rows.push(["订单", sellOrder.Symbol, sellOrder.Type == ORDER_TYPE_BUY ? "买入" : "卖出", sellOrder.Price, sellOrder.Amount])
}
if (pos && pos.length == 1) {
tbl.rows.push(["持仓", pos[0].Symbol, pos[0].Type == PD_LONG ? "多" : "空", pos[0].Price, pos[0].Amount])
}
logStatusMsgBuff = "当前权益:" + acc.Equity + ", 初始权益:" + totalEq + (!IsVirtual() ? ", 浮动盈亏:" + (acc.Equity - totalEq) : "") + ", 加仓次数:" + addCounter + "\n`" + JSON.stringify(tbl) + "`"
LogStatus(_D(), "当前权益:", acc.Equity, ", 初始权益:", totalEq, !IsVirtual() ? ", 浮动盈亏:" + (acc.Equity - totalEq) : "", ", 加仓次数:" + addCounter, "\n`" + JSON.stringify(tbl) + "`")
plotRecords(c, buyOrder, sellOrder, pos)
Sleep(interval)
}
Sleep(interval)
}
}
function onexit() {
Log("扫尾,撤销所有挂单")
cancelAll()
}
对于FMZ平台的新同学来说,看到这样一堆代码是不是手足无措。与其我们一行一行研究代码,不如(偷个懒)扔给AI,让AI给我们讲解,提升学习效率。
AI讲解
我这里使用的是GPT,以下内容为AI讲解:
-
一、概述
本文将介绍一套基于EMA(指数移动平均线)趋势过滤的逐级加仓交易策略。
该策略主要应用在币安合约(Futures_Binance)上运行,支持1小时周期回测,基于1分钟K线数据聚合。
通过实时监控价格与EMA的偏离度,策略动态切换交易模式(正常交易/暂停交易),在平稳行情中逐步加仓,在波动异常时自动暂停以规避风险。 -
二、策略逻辑
-
- 趋势过滤(锁仓控制)
计算最近一小时的K线数据的60周期EMA。
当现价与EMA的偏离超过3%时,暂停下单并撤销挂单。
当偏离回落到2%以内时,恢复正常交易。
通过趋势过滤,避免在剧烈行情中频繁加仓导致爆仓风险。
- 趋势过滤(锁仓控制)
-
- 加仓逻辑(逐级加仓)
初始无仓位时,同时挂出低买与高卖两个挂单。
挂单价格:
买单挂在现价下方(targetProfit/5距离)
卖单挂在现价上方(targetProfit/5距离)
若挂单成交,则进行一次逐级加仓:
若买单成交,继续在更低价格挂入下一笔买单,同时设置已有持仓的卖出止盈单。
若卖单成交,继续在更高价格挂入下一笔卖单,同时设置已有持仓的买入止盈单。
加仓量可以设置是否翻倍或固定数量。
加仓次数达到maxAddCounter后,策略可以选择强制平仓清仓。
- 加仓逻辑(逐级加仓)
-
- 仓位控制与资金保护
根据初始总权益(totalEq)设置每次开仓的金额。
支持以USDT金额估算张数(自动按标的价格计算下单数量)。
保证账户剩余余额大于一定比例(reserve),若余额不足则停止交易。
- 仓位控制与资金保护
-
-
三、核心函数与模块介绍
-
- 获取账户总权益
对币安合约(Futures_Binance)和OKEX V5(Futures_OKCoin)分别封装了getTotalEquity函数。
兼容不同交易所API差异,统一返回账户当前总资产(USDT计价)。
- 获取账户总权益
-
- 下单与撤单模块
trade(direction, price, amount):封装统一的下单接口。
cancelAll():取消所有挂单,防止挂单过多导致风险积累。
- 下单与撤单模块
-
- 开多、开空与平仓操作
openLong(price, amount):开多。
openShort(price, amount):开空。
coverLong(price, amount):平多。
coverShort(price, amount):平空。
- 开多、开空与平仓操作
-
- 加仓次数与状态记录
使用 _G() 持久化记录加仓次数addCounter,避免断线重启后加仓状态丢失。
利用 LogStatus 动态展示实时账户信息、挂单情况和持仓信息。
- 加仓次数与状态记录
-
- 图表绘制
使用 KLineChart 绘制买入挂单价、卖出挂单价、持仓均价等关键线,辅助可视化回测与实时监控。
- 图表绘制
-
-
四、策略流程图
textflowchart TD 开始 --> 初始化参数 初始化参数 --> 账户余额检查 账户余额检查 --> 计算EMA 计算EMA --> 判断是否锁仓 判断是否锁仓 -->|是| 暂停交易 判断是否锁仓 -->|否| 正常交易 正常交易 --> 是否有持仓 是否有持仓 -->|无| 首次挂单 是否有持仓 -->|有| 按持仓方向加仓与止盈挂单 首次挂单 --> 等待成交 按持仓方向加仓与止盈挂单 --> 等待成交 等待成交 --> 挂单成交检测 挂单成交检测 --> 成交后调整 成交后调整 --> 更新加仓次数 更新加仓次数 --> 是否超出最大加仓次数 是否超出最大加仓次数 -->|是| 平仓 是否超出最大加仓次数 -->|否| 继续交易 -
五、总结
这套基于EMA过滤的逐级加仓策略,兼顾了收益和风险控制,适合在震荡偏弱波动的市场中运行。
通过灵活的加仓方式与趋势暂停机制,可以有效降低爆仓概率,并提升资金使用效率。
同时支持回测和实盘,模块化设计易于扩展,比如可以进一步引入动态调整targetProfit、amount,或者增加其他技术指标来增强过滤效果。- 要不要我顺便帮你加一个进阶版建议?比如:
如何改进成双向加仓(双向持仓)
如何动态调节加仓比例
如何嵌入风控止损逻辑?
- 要不要我顺便帮你加一个进阶版建议?比如:
局部提问/讲解
对于GPT提问:
以下内容为AI讲解:
在这个策略中,趋势过滤是通过以下部分实现的:
javascript
var ema = TA.EMA(r, 60)
if (Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] > 0.03) {
cancelAll()
isLock = true
}
if (Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] < 0.02) {
isLock = false
}
if (isLock) {
LogStatus(_D(), "暂停, 检测阈值:", _N(Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2], 3), logStatusMsgBuff)
plotRecords(c, null, null, pos)
Sleep(interval)
continue
}
-
代码分析
EMA 趋势过滤:
使用了 TA.EMA(r, 60) 计算过去60个小时的指数移动平均线(EMA)。r 是由 exchange.GetRecords 获取的历史 K 线数据,每个数据点代表一个小时的价格信息。ema 数组存储了每个小时的EMA值,ema[ema.length - 2] 代表当前时间点的前一个小时的EMA值。趋势锁定与解除:
通过比较当前价格(ticker.Last)与前一个小时的EMA值的差异,计算价格偏离EMA的百分比。
Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] 计算了当前价格与前一小时EMA的偏离百分比。
如果偏离超过3%(> 0.03),则认为市场的趋势变化较大,调用 cancelAll() 函数取消所有挂单,并将 isLock 设置为 true,即锁仓状态,阻止进一步的交易。
如果价格与EMA的偏离小于2%(< 0.02),则解除锁仓状态,将 isLock 设置为 false。暂停交易:
如果 isLock 为 true(表示当前处于锁仓状态),则不进行进一步的交易操作。
程序会输出日志记录,并调用 plotRecords() 绘制图表,展示当前的趋势和持仓情况。
通过 Sleep(interval) 暂停一定时间后继续循环。 -
回测测试
似乎是可以过滤掉一些趋势。通过自己阅读代码,发现AI讲解说明的很清晰,甚至比让我来讲解更加清晰。
总结
通过让AI分析讲解整体策略、然后根据AI对于策略整体的讲解,再逐步分析、拆解、讲解策略局部细节、讲解设计思路。得到的讲解内容是比较准确的,AI把策略中设计的细节几乎没有遗漏的都罗列了出来,通过进一步提问也会进行细致分析。这样学习整个策略的思路、代码设计细节、增加策略设计经验都是很有提升的。
在量化交易的道路上,AI可以成为我们极为有力的学习与成长伙伴。与其依赖AI一键生成成品策略,不如通过AI:
- 深度学习已有策略
- 快速理解复杂逻辑
- 系统提炼设计经验
- 不断优化与创新
这样的路径,才能真正提升我们的量化交易能力,建立属于自己的系统化交易体系。在发明者量化交易平台上,我们可以充分利用AI的力量,结合自己的实践,走得更远、飞得更高。
- 1



