With the rapid development of artificial intelligence (AI) technology, it has demonstrated extremely high efficiency and creativity in many fields. As a highly technical field, quantitative trading is also exploring the application of AI actively. However, in practice, we will find that there are still great challenges in simply relying on AI to directly generate complete, stable and sustainable profitable trading strategies.
Especially for new users on the platform, due to weak programming skills, it is difficult to implement their trading ideas into strategy codes. Although it is currently possible to tell AI the ideas and let AI output strategies. But the implementation effect is not quite as expected. I often encounter users who come to consult with AI-generated codes, and sometimes the AI-generated strategies can be seen through at a glance. Because there are still many problems with the strategy codes generated by AI at this stage, the use of AI in this way not only does not solve any problems, but also brings more confusion and problems to novices. It can be said that the more you learn, the more confused you become, and finally “from entry to giving up”.
The editor personally believes that there are two main reasons for the current problem of AI directly outputting strategies:
So, are there other more efficient application methods? This article wants to share a new way of thinking: let AI help us learn existing strategies, understand strategy design, extract key details and techniques, and further analyze their effectiveness and room for improvement. This method can not only help us grasp the essence of strategy design more quickly, but also systematically improve our quantitative trading level.
Taking advantage of AI, AI can understand specific code analysis more accurately, because for AI, code data is “1 is 1, 2 is 2” and there will be no logical confusion, ambiguity, or other problems caused by natural language description requirements. So why not use the advantages of AI to reduce manual workload and give full play to the advantages of manual labor?
It can be divided into the following steps:
Choose an existing strategy It can be one you wrote yourself, open source, or an excellent strategy sample on the FMZ Quant Platform.
Let AI help us explain the strategy Understand the overall idea Sort out the functional modules of each part Define the indicators, parameters, and trading logic used clearly
Ask further questions to guide AI to analyze the pros and cons of the strategy In what market conditions does the strategy perform better? What are the possible risk points? What areas can be optimized and improved?
Verify and adjust based on your own thinking Backtest on different products and different periods Add additional filtering conditions or risk control measures Observe performance changes and form your own insights
Let AI learn a strategy and explain it to us to see if it meets our expectations. It can help us learn quantitative analysis.
Design and implementation of step-by-step position increase trading strategy based on EMA trend filtering Strategy address: https://www.fmz.com/strategy/492116
/*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("Failed to obtain the total account equity!")
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("Failed to obtain the total account equity!")
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 "The exchange is not supported"
}
}
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("Reset all data", "#FF0000")
}
exchange.SetContractType(contractType)
exchange.SetPrecision(pricePrecision, amountPrecision)
Log("Setting the precision", 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 "Failed to obtain initial equity"
}
} 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 "Long and short positions at the same time"
}
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(), "Pause, detection threshold:", _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(), "The position increase limit has been reached", logStatusMsgBuff)
if (isMaxAddCounterClear && pos.length >= 1) {
Log("The position increase has reached the upper limit, cancel the order, clear the position")
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, "Current total equity:", 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("Buy order completed")
cancelAll()
break
} else if (!isFindSellId) {
Log("Sell order completed")
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(["order", buyOrder.Symbol, buyOrder.Type == ORDER_TYPE_BUY ? "buy" : "sell", buyOrder.Price, buyOrder.Amount])
}
if (sellOrder) {
tbl.rows.push(["order", sellOrder.Symbol, sellOrder.Type == ORDER_TYPE_BUY ? "buy" : "sell", sellOrder.Price, sellOrder.Amount])
}
if (pos && pos.length == 1) {
tbl.rows.push(["position", pos[0].Symbol, pos[0].Type == PD_LONG ? "long" : "short", pos[0].Price, pos[0].Amount])
}
logStatusMsgBuff = "Current equity:" + acc.Equity + ", Initial equity:" + totalEq + (!IsVirtual() ? ", Floating profit and loss:" + (acc.Equity - totalEq) : "") + ", Number of positions added:" + addCounter + "\n`" + JSON.stringify(tbl) + "`"
LogStatus(_D(), "Current equity:", acc.Equity, ", Initial equity:", totalEq, !IsVirtual() ? ", Floating profit and loss:" + (acc.Equity - totalEq) : "", ", Number of positions added:" + addCounter, "\n`" + JSON.stringify(tbl) + "`")
plotRecords(c, buyOrder, sellOrder, pos)
Sleep(interval)
}
Sleep(interval)
}
}
function onexit() {
Log("Clean up and cancel all pending orders")
cancelAll()
}
For novices of the FMZ platform, seeing such a pile of code may be overwhelming. Instead of studying the code line by line, it is better to throw it to AI (to be lazy) and let AI explain it to us to improve learning efficiency.
I am using GPT here, and the following content is explained by AI:
I. Overview This article will introduce a set of step-by-step position-adding trading strategies based on EMA (Exponential Moving Average) trend filtering. This strategy is mainly used on Futures_Binance, supports 1-hour period backtesting, and is based on 1-minute K-line data aggregation. By real-time monitoring of the deviation between price and EMA, the strategy dynamically switches trading modes (normal trading/suspended trading), gradually adds positions in a stable market, and suspends automatically when there is abnormal fluctuation to avoid risks.
II. Strategy logic
Trend filtering (lock position control) Calculate the 60-period EMA of the K-line data of the last hour. When the current price deviates from the EMA by more than 3%, suspend the order and cancel the pending order. When the deviation falls back to within 2%, resume normal trading. Through trend filtering, avoid the risk of liquidation caused by frequent increase of positions in turbulent market conditions.
Position adding logic (step-by-step position adding) When there is no position initially, place two pending orders, buy low and sell high at the same time. Pending order price: Buy order is placed below the current price (targetProfit/5 distance) Sell order is placed above the current price (targetProfit/5 distance) If the pending order is executed, a step-by-step position adding is performed: If the buy order is executed, continue to place the next buy order at a lower price, and set the sell stop profit order for the existing position at the same time. If the sell order is executed, continue to place the next sell order at a higher price, and set the buy stop profit order for the existing position at the same time. The position adding amount can be set to double or fixed. After the number of position adding reaches maxAddCounter, the strategy can choose to force liquidation.
Position control and capital protection Set the amount of each position opened based on the initial total equity (totalEq). Support estimating the number of contracts based on the USDT amount (calculating the order amount based on the underlying price automatically). Ensure that the remaining balance in the account is greater than a certain proportion (reserve). If the balance is insufficient, stop trading.
Compatible with API differences of different exchanges, return the current total assets of the account (denominated in USDT) uniformly.
Order placement and cancellation module trade(direction, price, amount): encapsulates a unified order placement interface. cancelAll(): cancel all pending orders to prevent risk accumulation caused by too many pending orders.
Open long, open short and close position operations openLong(price, amount): open long. openShort(price, amount): open short. coverLong(price, amount): close long. coverShort(price, amount): close short.
Number of position additions and status records Use _G() to persistently record the number of position additions addCounter to avoid loss of position addition status after disconnection and restart. Use LogStatus to dynamically display real-time account information, pending order status and position information.
PLot chart Use KLineChart to draw key lines such as buy order price, sell order price, average position price, etc. to assist in visual backtesting and real-time monitoring.
flowchart TD
Start --> Initialize parameters
Initialize parameters --> Check account balance
Check account balance --> Calculate EMA
Calculate EMA --> Determine whether to lock positions
Determine whether to lock positions -->|Yes| Suspend trading
Determine whether to lock positions -->|No| Normal trading
Normal trading --> Is there a position
Is there a position -->|No| First pending order
Is there a position -->|Yes| Add positions and take-profit pending orders according to the direction of positions
First pending order --> Wait for transaction
Add positions and take-profit pending orders according to the direction of positions --> Wait for transaction
Wait for transaction --> Order transaction detection
Order transaction detection --> Adjust after transaction
Adjust after transaction --> Update the number of positions added
Update the number of positions added --> Whether the maximum number of positions added has been exceeded
Whether the maximum number of positions added has been exceeded -->|Yes| Close the position
Whether the maximum number of positions added has been exceeded -->|No| Continue trading
Do you want me to add an advanced version of the suggestion for you? For example: How to improve to bidirectional position-adding (position bidirection)? How to adjust the position-adding ratio dynamically? How to embed risk control stop loss logic?
For GPT questions: Please explain to me in detail how this strategy is designed to filter trends, based on specific code analysis.
OK, let me explain in detail how this strategy designs trend filtering and how it is implemented in the code. We will focus on the following aspects:
EMA (Exponential Moving Average) trend filtering principle
How to calculate EMA and decide whether to suspend trading based on its deviation
Code implementation and strategy flow
The following content is explained by AI:
In this strategy, trend filtering is achieved by:
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(), "Pause, detection threshold:", _N(Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2], 3), logStatusMsgBuff)
plotRecords(c, null, null, pos)
Sleep(interval)
continue
}
Trend lock and release: By comparing the difference between the current price (ticker.Last) and the EMA value of the previous hour, calculate the percentage of price deviation from EMA. Math.abs(ticker.Last - ema[ema.length - 2]) / ema[ema.length - 2] calculates the percentage of deviation between the current price and the EMA of the previous hour. If the deviation exceeds 3% (> 0.03), it is considered that the market trend has changed significantly, and the cancelAll() function is called to cancel all pending orders, and isLock is set to true, that is, the locked position state, preventing further trading. If the price deviates from EMA by less than 2% (< 0.02), the locked position state is released and isLock is set to false.
Suspend trading: If isLock is true (indicating that it is currently in the locked position state), no further trading operations are performed. The program will output log records and call plotRecords() to draw a chart to show the current trend and position status. Sleep(interval) is used to pause for a certain period of time and then continue the loop.
It seems that some trends can be filtered out. By reading the code myself, I found that the AI explanation was very clear, even clearer than me explaining it.
By letting AI analyze and explain the overall strategy, and then based on AI’s explanation of the overall strategy, analyze, disassemble, and explain the local details of the strategy gradually, and explain the design ideas. The explanation content obtained is accurate. AI lists almost all the design details in the strategy without omissions, and will also conduct detailed analysis through further questions. In this way, learning the ideas of the entire strategy, code design details, and increasing strategy design experience is improved.
On the road of quantitative trading, AI can become our extremely powerful learning and growth partner. Instead of relying on AI to generate finished strategies with one click, it is better to use AI:
Only by following this path can we truly improve our quantitative trading capabilities and establish our own systematic trading system. On the FMZ Quant Trading Platform, we can make full use of the power of AI and combine it with our own practice to go further.