The Go-Anywhere Quantitative Journey Starts from FMZ
Introduction
Have you ever thought that quantitative trading can be learned easily and started immediately without writing code all night to build a framework, designing UI, and various design details and mechanisms? On the FMZ Quant Trading Platform, everything becomes possible. You don't need a deep programming background, nor do you have to worry about complicated deployment processes - all you need is a computer and an account to start your "go-anywhere" quantitative journey. This article will take you from 0 to quickly get started with FMZ, feel the charm of automated trading, and use data and strategies to master the market rhythm. Whether you are a beginner or a veteran seeking to improve efficiency, this journey is worth a try.
Confusion of Quantitative Trading Beginners
I often communicate and chat with platform beginners. Quantitative trading beginners are usually confused by the complete design process. They often have no idea where to start and are at a loss when they have trading ideas.
Confused about:
- How to design opening and closing positions
- How to design profits calculation
- How to design strategies to restart and continue trading progress
- How to design a strategy chart display
- How to design strategy interaction control
Let's solve the above confusion together.
Design Explanation
In the world of quantitative trading, strategy design is often an exploration journey with no end. You may have tried to write indicators, or blindly follow buy and sell signals, but the ones that can really go far are those strategy systems that can be "visible, adjustable, and stable". Based on the FMZ platform, let's have a "go-anywhere" practical experience. Build a simple strategy, from parameter settings, chart display, to interactive functions and profit and loss calculations, to fully meet the design requirements of a strategy.
The strategy idea is a step-by-step position-increasing strategy based on ATR, step-by-step grid position building logic (bidirectional for long and short), ATR adaptive volatility calculation, and position liquidation logic (when the market reverses to the central axis).
The strategy is based on the following design requirements:
Add Positions and Close Positions According to Price Breakthroughs at Different Levels
Set up two arrays to control the gradual addition of positions.
var arrUp = null
var arrDown = null
After each position is added, the position information is pushed into the array, which is convenient for controlling the position and displaying the data on the strategy live trading interface.
Open and close positions according to the price breakthrough level; for the sake of simplicity, both opening and closing positions use market orders, which are simple and effective.
if (close > up && i >= arrUp.length && !isPaused) {
var id = exchange.CreateOrder(symbol, "sell", -1, tradeAmount)
if (!id) {
Log("Order failed")
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("Order failed")
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)
}
Closing positions is handled by a function. Some data structures need to be reset each time the position is closed, so the closing function needs to be encapsulated into a function for reuse in the interactive module.
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("Order failed")
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("Order failed")
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)
}
Position Allocation at Different Levels
It is divided into multiple levels, the maximum level is: maxRatio. Each level calculates a different price threshold.
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
}
// ...
}
Support Dynamic Parameter Adjustment, Pause Operation, Quick Liquidation and Other Interactions
Design interactive functions, liquidation, pause, unpause, modify parameters, etc. It is very convenient to design interactions on FMZ, and the platform provides many interactive controls. We only need to add interactive controls to the strategy, and then write various recognition and processing codes when receiving messages in the strategy code.
var cmd = GetCommand()
if (cmd) {
Log("Interactive commands:", cmd)
var arrCmd = cmd.split(":")
if (arrCmd.length == 2) {
var strCmd = arrCmd[0]
var param = parseFloat(arrCmd[1])
if (strCmd == "atrPeriod") {
atrPeriod = param
Log("Modify ATR parameters:", atrPeriod)
}
} else {
if (cmd == "isPaused" && !isPaused) {
isPaused = true
Log("Trading suspension")
} else if (cmd == "isPaused" && isPaused) {
isPaused = false
Log("Unsuspend trading")
} else if (cmd == "clearAndPaused") {
clear(pos, r)
isPaused = true
Log("Liquidation and suspension of trading")
}
}
}
It Has An Opening/Closing Reminder Mechanism
When a strategy opens or closes a position, you can easily push messages to email, FMZ APP, third-party interfaces, etc. on FMZ.
Log([r[r.length - 1].Time, "long", close, tradeAmount], "@") // Message push
Receive push notifications (FMZ APP and other apps will also receive push notifications):
Real-Time Statistics and Display of Profits and Positions
The function for calculating profit and loss is called every time a position is closed, calculates profit and loss and outputs the profit and loss curve.
function calcProfit() {
var initAcc = _G("initAcc")
var nowAcc = _C(exchange.GetAccount)
var profit = nowAcc.Equity - initAcc.Equity
return profit
}
Support State Persistence (Breakpoint Recovery)
Using the _G()
function on FMZ, it is easy to design a strategy progress recovery mechanism.
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)
}
Design of Placing Orders by Amount
When trading contracts, the order amount of the order interface is the number of contracts, so there are often user requirements on how to place orders by the number of U:
if (isAmountForUSDT) {
tradeAmount = tradeAmount * 1.05 / close
}
tradeAmount = _N(tradeAmount, amountPrecision)
It’s actually very simple, just divide the amount by the price.
Reserve Ratio Design
If you want your account to always reserve a certain amount of funds as risk control, you can design this simple mechanism.
var balance = acc.Balance
if (balance - initAcc.Equity * reserve < tradeAmount * close) {
continue
}
Visualized Charts
When running the live trading, we must observe the strategy, including account equity, strategy status, strategy positions, order information, market charts, etc. These are designed as follows:
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(), "Initial equity:" + initAcc.Equity, ", Current equity:" + acc.Equity, ", Running status:" + (isPaused ? "Trading suspension" : "Running"),
"\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")
In the end, more than 200 lines of code implemented a complete strategy that can be backtested and put into practice. We achieved our ultimate goal: to build a "visualization + interaction + automation" and multi-in-one quantitative trading system on FMZ.
Strategy Running Effect and Backtesting Results
Backtesting is for reference only. Those who do quantitative trading know that "backtesting" cannot 100% simulate the actual scenario. Backtesting is more used to test strategy logic, strategy robustness, basic function testing, etc.
Strategy Code, Parameter Design
Parameter design:
Interaction Design:
Strategy source code:
/*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("Order failed")
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("Order failed")
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("Order failed")
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("Order failed")
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("Interactive command:", cmd)
var arrCmd = cmd.split(":")
if (arrCmd.length == 2) {
var strCmd = arrCmd[0]
var param = parseFloat(arrCmd[1])
if (strCmd == "atrPeriod") {
atrPeriod = param
Log("Modify ATR parameters:", atrPeriod)
}
} else {
if (cmd == "isPaused" && !isPaused) {
isPaused = true
Log("Trading suspension")
} else if (cmd == "isPaused" && isPaused) {
isPaused = false
Log("Unsuspend trading")
} else if (cmd == "clearAndPaused") {
clear(pos, r)
isPaused = true
Log("Liquidation and suspension of trading")
}
}
}
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(), "Initial equity:" + initAcc.Equity, ", Current equity:" + acc.Equity, ", Running status:" + (isPaused ? "Trading suspension" : "Running"),
"\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")
Sleep(5000)
}
}
The strategy is only for teaching purposes. Although it can be used in live trading and is currently profitable, it will take time to test its long-term effectiveness. There is still room for optimization in the strategy plot part, which can avoid some repeated operations and improve program efficiency. The strategy logic can also be further optimized.
Live Trading is A Long Journey
A poetic summary from GPT:
Live trading is a long journey, not asking for a return date, just seeking peace of mind. Every time you open a position, you are sowing the light of hope in the vast market; every time you stop loss, you are learning to move forward more firmly in the wind and rain. The market is like a tide, and profits and losses are like dreams. We dance on the crest of the waves of numbers and watch under the lighthouse of strategy. I hope that you and I can neither lose our way nor fear loneliness in this long journey, and finally reach our own piece of light.
Summary: From Strategy Development to System Thinking
This article not only introduces a complete strategy, but more importantly, a "systematic" strategy development idea. From strategy design, state management, risk control, chart interaction, to actual practice implementation, this is a set of templates that can be reused repeatedly, and it is also the only way for quantitative trading to become professional.
I hope you can use the FMZ platform to build your own automated trading system so that you will not miss any signal.
Thank you for your reading and support. The strategy is only for teaching purposes, please use it with caution in live trading.