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

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

创建于: 2026-01-23 11:09:58, 更新于: 2026-01-23 15:06:05
comments   0
hits   7

[TOC]

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

Preface

Recently, FMZ Quant Platform officially integrated Lighter DEX. Honestly, when I first saw this news, I didn’t pay much attention—after all, there are countless DEXs on the market. But when I looked into it more closely, one feature caught my eye: zero trading fees.

Yes, you read that right. Lighter DEX implements a zero-fee policy for regular traders. This immediately made me think of something: those strategies I had “shelved” over the years because of fee erosion—could they finally see the light of day again? So I opened FMZ’s strategy square and started digging through those “ancient” strategies…

1. Lighter

I won’t go into too much detail about this new DEX. If you’re interested, you can look up some materials to learn more. Currently, the user experience is quite good. From my experience, Lighter delivers centralized exchange-level performance while maintaining decentralized transparency, and it’s also quite stable for high-frequency API access.

Resource Summary

Here’s a collection of useful resources:

Configuration

Next, let me briefly introduce how to configure Lighter on FMZ. Add Lighter’s API KEY configuration on the FMZ platform’s “Add Exchange” page.

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

Three items need to be configured:

Signing API Private Key

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

This is NOT your wallet private key. This signing private key is created after logging into Lighter, specifically for placing orders and trading. Log in to https://app.lighter.xyz, connect your wallet, and then create it.

If you log into Lighter’s test environment, you’ll create a testnet KEY—I won’t elaborate on this.

When creating the signing private key, you need to specify an index. 0~2 are reserved positions, so start from 3.

All login and creation steps require wallet signatures. I used the Binance wallet and the experience was quite good.

Configure the created signing private key in the corresponding input field on FMZ’s “Add Exchange” page.

Important Note:

You need to download the latest version of the docker program to support Lighter futures exchange.

API Index

Fill in the index corresponding to the signing private key.

Account Index

The account index can be obtained through Lighter’s API:

API endpoint: https://mainnet.zklighter.elliot.ai/api/v1/account?by=l1_address&value={wallet_address}

For example, access this address directly in your browser: https://mainnet.zklighter.elliot.ai/api/v1/account?by=l1_address&value=0x123xxxx, where 0x123xxxx is an example address—replace it with your actual address.

Returned data:

{
    "code": 200,
    "total": 1,
    "accounts": [
        {
            "code": 0,
            "account_type": 0,
            "index": 1234567,
            "l1_address": "0x123xxxx",

The "index": 1234567 is your account index.

You must deposit assets (e.g., USDC) to initialize and create a Lighter account.

Note: Using the mainnet endpoint API returns the mainnet account index; using the testnet endpoint returns the testnet account index.

Why do you need this account index? Because Lighter has a sub-account system that distinguishes different accounts by account index. If you need to use a sub-account, configure that sub-account’s account index here (the sub-account’s account index will appear in the JSON data returned by the query link above).

2. Ancient Strategies on FMZ

Zero Fees: A Game Changer

This is the feature that excites me most. Lighter implements a zero-fee policy for regular traders.

What does zero fees mean?

  • High-frequency strategies are no longer “eaten” by fees
  • Grid strategies can set tighter grid spacing
  • Arbitrage strategies have a much lower break-even point
  • Those strategies that were “theoretically viable but defeated by fees in practice” finally have their place

Digging Up Ancient Strategies: The Temptation of Zero Fees

Strategy Archive Excavation

With the idea of “what strategies could zero fees revive,” I started searching through FMZ’s strategy library and community. Soon, a familiar strategy caught my eye:

https://www.fmz.com/digest-topic/1211

This strategy is simple and aggressive, but in an environment with fees, it basically guarantees 100% losses. However, in Lighter’s zero-fee environment, it’s worth a try.

So Here’s the Problem

The code is too old. Looking at the strategy, the code is simple and crude, the UI displays almost nothing, and it lacks auxiliary functions. Here are a few things missing:

  • Some API interfaces are outdated.
  • Lacks modern risk control modules.
  • Lacks display information.
  • Lacks profit statistics.

Manually refactor all this code? Honestly, it’s a headache. But I thought of a “lazy” approach—let AI help me refactor.

3. Strategy Refactoring

Let me put up a “shield” first: this article is just an exploratory attempt with an emerging DEX exchange + AI strategy refactoring. I cannot guarantee how profitable the refactored strategy will be. So let’s start using Claude to refactor this ancient strategy. I don’t want to write code from scratch. My requirements are:

  • Preserve the original strategy’s core logic
  • Adapt to FMZ’s latest API specifications
  • Add necessary risk control and logging modules
  • Have relatively complete and clear information display
  • Appropriately optimize code logic

Throwing the Original Code to AI

I copied out the original strategy code and sent it to AI along with my requirements. After several rounds of dialogue and adjustments, AI helped me generate the refactored code:

I won’t post all the back-and-forth with AI. In short, it was: state requirements, test, if unsatisfied then continue stating requirements.

You can even use:

while :; do cat PROMPT.md | claude-code ; done

:)(borrowed idea)

/*
High-Frequency Market Making Strategy - FMZ Futures Version (with Position Correction)

Strategy Parameters:
- sleeptime: Sleep time (milliseconds) - Default 3500
- floatamountbuy: Buy order depth threshold - Default 20
- floatamountsell: Sell order depth threshold - Default 20
- diffprice: Minimum arbitrage spread - Default 50
- baseAmount: Base order quantity - Default 0.1
- maxPosition: Maximum single-side position - Default 10
- stopLossRatio: Stop loss ratio - Default 0.9 (90%)
- closeOnExit: Close positions on exit - Default false
*/

// Global variables
var pricePrecision = 2;
var amountPrecision = 3;
var tickSize = 0.01;
var minQty = 0.001;
var symbol = "ETH_USDT.swap";

// Statistics
var stats = {
    startTime: 0,
    cycleCount: 0,
    orderCount: 0,
    cancelCount: 0,
    initialEquity: 0,
    currentEquity: 0,
    maxEquity: 0,
    maxDrawdown: 0,
    isStopLoss: false,
    lastLogCleanTime: 0,
    lastEmergencyTime: 0
};

// Log cleanup config
var LOG_CLEAN_INTERVAL = 60 * 60 * 1000;
var LOG_RESERVE_COUNT = 10000;
var EMERGENCY_COOLDOWN = 5000;

// ==================== Precision Utility Functions ====================

/**
 * Infer precision from value (decimal places)
 * Example: 0.005 -> 3, 0.0001 -> 4, 0.1 -> 1, 1 -> 0
 */
function GetPrecisionFromValue(value) {
    if (!value || value >= 1) return 0;
    var str = value.toString();
    
    // Handle scientific notation (e.g., 1e-4)
    if (str.indexOf('e') !== -1) {
        var match = str.match(/e-(\d+)/);
        return match ? parseInt(match[1]) : 0;
    }
    
    // Handle regular decimals
    if (str.indexOf('.') === -1) return 0;
    return str.split('.')[1].length;
}

/**
 * Normalize order quantity
 * 1. Round according to precision
 * 2. Ensure it's an integer multiple of minQty
 * 3. Not less than minQty
 */
function NormalizeAmount(amount) {
    if (amount <= 0) return 0;
    
    // Round according to precision
    var normalized = _N(amount, amountPrecision);
    
    // Ensure it's an integer multiple of minQty
    if (minQty > 0) {
        normalized = Math.floor(normalized / minQty) * minQty;
        normalized = _N(normalized, amountPrecision);
    }
    
    // Check minimum order quantity
    if (normalized < minQty) {
        return 0;
    }
    
    return normalized;
}

/**
 * Normalize price
 */
function NormalizePrice(price) {
    if (tickSize > 0) {
        price = Math.round(price / tickSize) * tickSize;
    }
    return _N(price, pricePrecision);
}

// Cancel all orders
function CancelPendingOrders() {
    var orders = _C(exchange.GetOrders, symbol);
    var count = 0;
    for (var j = 0; j < orders.length; j++) {
        exchange.CancelOrder(orders[j].Id, orders[j]);
        count++;
    }
    if (count > 0) {
        stats.cancelCount += count;
    }
    return count;
}

// Calculate order price
function GetPrice(Type, depth) {
    var amountBids = 0;
    var amountAsks = 0;
    
    if (Type == "Buy") {
        for (var i = 0; i < 20 && i < depth.Bids.length; i++) {
            amountBids += depth.Bids[i].Amount;
            if (amountBids > floatamountbuy) {
                return NormalizePrice(depth.Bids[i].Price + tickSize);
            }
        }
    }
    
    if (Type == "Sell") {
        for (var j = 0; j < 20 && j < depth.Asks.length; j++) {
            amountAsks += depth.Asks[j].Amount;
            if (amountAsks > floatamountsell) {
                return NormalizePrice(depth.Asks[j].Price - tickSize);
            }
        }
    }
    
    return NormalizePrice(depth.Asks[0].Price);
}

// Get position information
function GetPosition() {
    var positions = _C(exchange.GetPositions, symbol);
    var pos = {
        long: { amount: 0, price: 0, profit: 0 },
        short: { amount: 0, price: 0, profit: 0 }
    };
    
    for (var i = 0; i < positions.length; i++) {
        var p = positions[i];
        if (p.Type === PD_LONG || p.Type === 0) {
            pos.long.amount = p.Amount;
            pos.long.price = p.Price;
            pos.long.profit = p.Profit || 0;
        } else {
            pos.short.amount = p.Amount;
            pos.short.price = p.Price;
            pos.short.profit = p.Profit || 0;
        }
    }
    
    return pos;
}

// Calculate account equity
function GetEquity(account, pos) {
    var equity = account.Balance + account.FrozenBalance + pos.long.profit + pos.short.profit;
    return equity;
}

// Check if funds are sufficient for order
function CheckFunds(account, price, amount, leverage) {
    leverage = leverage || 10;
    var requiredMargin = (price * amount) / leverage;
    var availableBalance = account.Balance;
    var safeBalance = availableBalance * 0.9;
    return safeBalance >= requiredMargin;
}

// ==================== Position Correction Mechanism ====================

/**
 * Calculate dynamic opening quantity (negative feedback mechanism)
 */
function CalcOpenAmount(pos, direction) {
    var currentPos = (direction === "long") ? pos.long.amount : pos.short.amount;
    var posRatio = currentPos / maxPosition;
    
    if (currentPos >= maxPosition) {
        return 0;
    }
    
    var amount = baseAmount;
    
    if (posRatio > 0.8) {
        amount = baseAmount * 0.2;
    } else if (posRatio > 0.6) {
        amount = baseAmount * 0.4;
    } else if (posRatio > 0.4) {
        amount = baseAmount * 0.6;
    } else if (posRatio > 0.2) {
        amount = baseAmount * 0.8;
    }
    
    var netPos = pos.long.amount - pos.short.amount;
    if (direction === "long" && netPos > maxPosition * 0.3) {
        amount = amount * 0.5;
    } else if (direction === "short" && netPos < -maxPosition * 0.3) {
        amount = amount * 0.5;
    }
    
    return NormalizeAmount(amount);
}

/**
 * Calculate dynamic closing quantity
 */
function CalcCloseAmount(pos, direction) {
    var currentPos = (direction === "long") ? pos.long.amount : pos.short.amount;
    
    if (currentPos <= 0) return 0;
    
    var posRatio = currentPos / maxPosition;
    var amount = baseAmount;
    
    if (posRatio > 1.0) {
        amount = currentPos;
    } else if (posRatio > 0.8) {
        amount = baseAmount * 3.0;
    } else if (posRatio > 0.6) {
        amount = baseAmount * 2.0;
    } else if (posRatio > 0.4) {
        amount = baseAmount * 1.5;
    }
    
    amount = Math.min(amount, currentPos);
    return NormalizeAmount(amount);
}

/**
 * Calculate closing price (more aggressive when position is heavy)
 */
function CalcClosePrice(pos, depth, direction) {
    var currentPos = (direction === "long") ? pos.long.amount : pos.short.amount;
    var posRatio = currentPos / maxPosition;
    
    if (direction === "long") {
        if (posRatio > 1.0) {
            return NormalizePrice(depth.Bids[0].Price - tickSize * 3);
        } else if (posRatio > 0.8) {
            return NormalizePrice(depth.Bids[0].Price);
        } else if (posRatio > 0.5) {
            var midPrice = (depth.Bids[0].Price + depth.Asks[0].Price) / 2;
            return NormalizePrice(midPrice);
        }
        return NormalizePrice(depth.Asks[0].Price - tickSize);
    } else {
        if (posRatio > 1.0) {
            return NormalizePrice(depth.Asks[0].Price + tickSize * 3);
        } else if (posRatio > 0.8) {
            return NormalizePrice(depth.Asks[0].Price);
        } else if (posRatio > 0.5) {
            var midPrice = (depth.Bids[0].Price + depth.Asks[0].Price) / 2;
            return NormalizePrice(midPrice);
        }
        return NormalizePrice(depth.Bids[0].Price + tickSize);
    }
}

/**
 * Emergency position reduction
 */
function EmergencyReduce(pos, depth) {
    var now = new Date().getTime();
    if (now - stats.lastEmergencyTime < EMERGENCY_COOLDOWN) {
        return false;
    }
    
    var needReduce = false;
    
    if (pos.long.amount > maxPosition * 1.2) {
        var reduceAmount = NormalizeAmount(pos.long.amount - maxPosition);
        if (reduceAmount > 0) {
            Log("⚠️ Long position severely exceeded (" + pos.long.amount + "/" + maxPosition + "), market reduce: " + reduceAmount, "#FF0000");
            var orderId = exchange.CreateOrder(symbol, "closebuy", -1, reduceAmount);
            if (orderId) {
                stats.orderCount++;
                stats.lastEmergencyTime = now;
            }
            needReduce = true;
        }
    }
    
    if (pos.short.amount > maxPosition * 1.2) {
        var reduceAmount = NormalizeAmount(pos.short.amount - maxPosition);
        if (reduceAmount > 0) {
            Log("⚠️ Short position severely exceeded (" + pos.short.amount + "/" + maxPosition + "), market reduce: " + reduceAmount, "#FF0000");
            var orderId = exchange.CreateOrder(symbol, "closesell", -1, reduceAmount);
            if (orderId) {
                stats.orderCount++;
                stats.lastEmergencyTime = now;
            }
            needReduce = true;
        }
    }
    
    if (needReduce) {
        Sleep(1000);
    }
    
    return needReduce;
}

function IsPositionOverload(pos) {
    return pos.long.amount > maxPosition || pos.short.amount > maxPosition;
}

// ==================== Log Cleanup ====================

function CleanLogs() {
    var now = new Date().getTime();
    if (now - stats.lastLogCleanTime > LOG_CLEAN_INTERVAL) {
        LogReset(LOG_RESERVE_COUNT);
        stats.lastLogCleanTime = now;
        Log("Logs cleaned, keeping last " + LOG_RESERVE_COUNT + " entries", "#0000FF");
    }
}

// ==================== Position Closing Related ====================

function CloseAllPositions(reason) {
    reason = reason || "Manual trigger";
    Log("========== Triggering Close All [" + reason + "] ==========", "#FF0000");
    
    CancelPendingOrders();
    Sleep(500);
    
    var pos = GetPosition();
    
    if (pos.long.amount > 0) {
        var orderId = exchange.CreateOrder(symbol, "closebuy", -1, pos.long.amount);
        if (orderId) {
            Log("Market close long: " + pos.long.amount, "#FF0000");
        }
    }
    
    if (pos.short.amount > 0) {
        var orderId = exchange.CreateOrder(symbol, "closesell", -1, pos.short.amount);
        if (orderId) {
            Log("Market close short: " + pos.short.amount, "#FF0000");
        }
    }
    
    Sleep(2000);
    
    var finalPos = GetPosition();
    if (finalPos.long.amount > 0 || finalPos.short.amount > 0) {
        Log("⚠️ Warning: Still have open positions! Long: " + finalPos.long.amount + ", Short: " + finalPos.short.amount, "#FF0000");
        if (finalPos.long.amount > 0) {
            exchange.CreateOrder(symbol, "closebuy", -1, finalPos.long.amount);
        }
        if (finalPos.short.amount > 0) {
            exchange.CreateOrder(symbol, "closesell", -1, finalPos.short.amount);
        }
        Sleep(1000);
    } else {
        Log("✅ All positions closed", "#00FF00");
    }
    
    Log("========== Close All Complete ==========", "#FF0000");
}

function CheckStopLoss(equity) {
    if (stats.isStopLoss) {
        return true;
    }
    
    var threshold = stats.initialEquity * stopLossRatio;
    if (equity < threshold) {
        stats.isStopLoss = true;
        var lossPercent = ((stats.initialEquity - equity) / stats.initialEquity * 100).toFixed(2);
        Log("⚠️ Stop loss triggered! Current equity: " + _N(equity, 4) + " USDT, Loss: " + lossPercent + "%", "#FF0000");
        return true;
    }
    
    return false;
}

function UpdateProfitChart(equity) {
    var profit = equity - stats.initialEquity;
    
    if (equity > stats.maxEquity) {
        stats.maxEquity = equity;
    }
    
    var drawdown = (stats.maxEquity - equity) / stats.maxEquity * 100;
    if (drawdown > stats.maxDrawdown) {
        stats.maxDrawdown = drawdown;
    }
    
    LogProfit(_N(profit, 4), "&");
}

function UpdateStatus(account, pos, depth, buyPrice, sellPrice, equity) {
    var runTime = (new Date().getTime() - stats.startTime) / 1000 / 60;
    var hours = Math.floor(runTime / 60);
    var mins = Math.floor(runTime % 60);
    
    var spread = sellPrice - buyPrice;
    var marketSpread = depth.Asks[0].Price - depth.Bids[0].Price;
    
    var profit = equity - stats.initialEquity;
    var profitPercent = (profit / stats.initialEquity * 100).toFixed(2);
    var drawdown = stats.maxEquity > 0 ? ((stats.maxEquity - equity) / stats.maxEquity * 100).toFixed(2) : 0;
    
    var longRatio = (pos.long.amount / maxPosition * 100).toFixed(1);
    var shortRatio = (pos.short.amount / maxPosition * 100).toFixed(1);
    var netPos = _N(pos.long.amount - pos.short.amount, amountPrecision);
    
    var table1 = {
        type: "table",
        title: "💰 Account Info",
        cols: ["Item", "Value"],
        rows: [
            ["Available Balance", _N(account.Balance, 4) + " USDT"],
            ["Frozen Balance", _N(account.FrozenBalance, 4) + " USDT"],
            ["Current Equity", _N(equity, 4) + " USDT"],
            ["Initial Equity", _N(stats.initialEquity, 4) + " USDT"],
            ["Peak Equity", _N(stats.maxEquity, 4) + " USDT"]
        ]
    };
    
    var table2 = {
        type: "table",
        title: "📈 Profit Statistics",
        cols: ["Item", "Value"],
        rows: [
            ["Cumulative Profit", _N(profit, 4) + " USDT"],
            ["Return Rate", profitPercent + " %"],
            ["Max Drawdown", drawdown + " %"],
            ["Stop Loss Threshold", (stopLossRatio * 100) + " %"],
            ["Stop Loss Status", stats.isStopLoss ? "⚠️ Triggered" : "✅ Normal"]
        ]
    };
    
    var longStatus, shortStatus;
    if (longRatio > 120) {
        longStatus = "🔴 Emergency Reduce";
    } else if (longRatio > 100) {
        longStatus = "🟠 Exceeded";
    } else if (longRatio > 80) {
        longStatus = "🟡 Controlled";
    } else {
        longStatus = "🟢 Normal";
    }
    
    if (shortRatio > 120) {
        shortStatus = "🔴 Emergency Reduce";
    } else if (shortRatio > 100) {
        shortStatus = "🟠 Exceeded";
    } else if (shortRatio > 80) {
        shortStatus = "🟡 Controlled";
    } else {
        shortStatus = "🟢 Normal";
    }
    
    var table3 = {
        type: "table",
        title: "📊 Position Info (Position Correction)",
        cols: ["Direction", "Quantity", "Limit", "Usage", "Status", "Unrealized PnL"],
        rows: [
            ["Long", pos.long.amount, maxPosition, longRatio + "%", longStatus, _N(pos.long.profit, 4)],
            ["Short", pos.short.amount, maxPosition, shortRatio + "%", shortStatus, _N(pos.short.profit, 4)],
            ["Net", netPos, "-", "-", netPos > 0 ? "Long Bias" : (netPos < 0 ? "Short Bias" : "Balanced"), "-"]
        ]
    };
    
    var table4 = {
        type: "table",
        title: "🎯 Market Making Info",
        cols: ["Item", "Value"],
        rows: [
            ["Best Bid", _N(depth.Bids[0].Price, pricePrecision)],
            ["Best Ask", _N(depth.Asks[0].Price, pricePrecision)],
            ["Market Spread", _N(marketSpread, pricePrecision)],
            ["Order Buy Price", _N(buyPrice, pricePrecision)],
            ["Order Sell Price", _N(sellPrice, pricePrecision)],
            ["MM Spread", _N(spread, pricePrecision)]
        ]
    };
    
    var table5 = {
        type: "table",
        title: "⏱️ Runtime Statistics",
        cols: ["Item", "Value"],
        rows: [
            ["Runtime", hours + "h " + mins + "m"],
            ["Cycle Count", stats.cycleCount],
            ["Order Count", stats.orderCount],
            ["Cancel Count", stats.cancelCount],
            ["Sleep Time", sleeptime + " ms"]
        ]
    };
    
    var table6 = {
        type: "table",
        title: "⚙️ Precision & Parameters",
        cols: ["Parameter", "Value"],
        rows: [
            ["Trading Symbol", symbol],
            ["Price Precision", pricePrecision],
            ["Amount Precision", amountPrecision],
            ["Min Order Qty", minQty],
            ["Tick Size", tickSize],
            ["Base Order Qty", baseAmount],
            ["Max Position", maxPosition],
            ["Close On Exit", closeOnExit ? "✅ Yes" : "❌ No"]
        ]
    };
    
    var statusIcon = stats.isStopLoss ? "🛑 Stopped" : (IsPositionOverload(pos) ? "⚠️ Position Overload" : "🤖 Running");
    
    var statusStr = statusIcon + " | " + _D() + " | Profit: " + _N(profit, 2) + " USDT (" + profitPercent + "%)\n";
    statusStr += "Long: " + pos.long.amount + "/" + maxPosition + " (" + longRatio + "%) | ";
    statusStr += "Short: " + pos.short.amount + "/" + maxPosition + " (" + shortRatio + "%) | Net: " + netPos + "\n";
    statusStr += "`" + JSON.stringify([table1, table2, table3, table4, table5, table6]) + "`";
    
    LogStatus(statusStr);
}

// Main trading logic
function onTick() {
    stats.cycleCount++;
    CleanLogs();
    
    var depth = _C(exchange.GetDepth);
    if (!depth || !depth.Bids || !depth.Asks || depth.Bids.length < 20 || depth.Asks.length < 20) {
        Log("Insufficient depth data, skipping this round");
        Sleep(1000);
        return;
    }
    
    var account = _C(exchange.GetAccount);
    var pos = GetPosition();
    var equity = GetEquity(account, pos);
    stats.currentEquity = equity;
    
    if (stats.cycleCount % 10 === 0) {
        UpdateProfitChart(equity);
    }
    
    var buyPrice = GetPrice("Buy", depth);
    var sellPrice = GetPrice("Sell", depth);
    
    if ((sellPrice - buyPrice) <= diffprice) {
        buyPrice = NormalizePrice(buyPrice - diffprice/2);
        sellPrice = NormalizePrice(sellPrice + diffprice/2);
    }
    
    UpdateStatus(account, pos, depth, buyPrice, sellPrice, equity);
    
    if (CheckStopLoss(equity)) {
        if (pos.long.amount > 0 || pos.short.amount > 0) {
            CloseAllPositions("Stop loss triggered");
        } else {
            CancelPendingOrders();
        }
        Log("Strategy stopped due to stop loss. To continue, please manually restart the strategy.", "#FF0000");
        return;
    }
    
    CancelPendingOrders();
    EmergencyReduce(pos, depth);
    
    pos = GetPosition();
    
    var openLongAmount = CalcOpenAmount(pos, "long");
    var openShortAmount = CalcOpenAmount(pos, "short");
    var closeLongAmount = CalcCloseAmount(pos, "long");
    var closeShortAmount = CalcCloseAmount(pos, "short");
    var closeLongPrice = CalcClosePrice(pos, depth, "long");
    var closeShortPrice = CalcClosePrice(pos, depth, "short");
    
    if (openLongAmount > 0 && CheckFunds(account, buyPrice, openLongAmount)) {
        var orderId = exchange.CreateOrder(symbol, "buy", buyPrice, openLongAmount);
        if (orderId) stats.orderCount++;
    }
    
    if (pos.short.amount > 0 && closeShortAmount > 0) {
        var orderId = exchange.CreateOrder(symbol, "closesell", closeShortPrice, closeShortAmount);
        if (orderId) stats.orderCount++;
    }
    
    if (openShortAmount > 0 && CheckFunds(account, sellPrice, openShortAmount)) {
        var orderId = exchange.CreateOrder(symbol, "sell", sellPrice, openShortAmount);
        if (orderId) stats.orderCount++;
    }
    
    if (pos.long.amount > 0 && closeLongAmount > 0) {
        var orderId = exchange.CreateOrder(symbol, "closebuy", closeLongPrice, closeLongAmount);
        if (orderId) stats.orderCount++;
    }
}

// Initialize trading precision
function InitPrecision() {
    try {
        var markets = exchange.GetMarkets();
        if (markets && markets[symbol]) {
            var market = markets[symbol];
            
            // Get basic precision info
            pricePrecision = market.PricePrecision || 2;
            tickSize = market.TickSize || 0.01;
            minQty = market.MinQty || 0.001;
            
            // Infer amount precision from MinQty
            // Example: MinQty=0.005 -> precision 3, MinQty=0.0001 -> precision 4
            var minQtyPrecision = GetPrecisionFromValue(minQty);
            
            // Infer price precision from TickSize (as backup verification)
            var tickSizePrecision = GetPrecisionFromValue(tickSize);
            
            // Amount precision takes the smaller of AmountPrecision and MinQty-inferred precision
            var declaredAmountPrecision = market.AmountPrecision || 8;
            amountPrecision = Math.min(declaredAmountPrecision, minQtyPrecision);
            
            // Price precision takes the smaller of PricePrecision and TickSize-inferred precision
            pricePrecision = Math.min(pricePrecision, tickSizePrecision);
            
            Log("========== Precision Info ==========");
            Log("Market returned - PricePrecision:", market.PricePrecision, ", AmountPrecision:", market.AmountPrecision);
            Log("Market returned - TickSize:", tickSize, ", MinQty:", minQty);
            Log("Inferred precision - TickSize precision:", tickSizePrecision, ", MinQty precision:", minQtyPrecision);
            Log("Final used - Price precision:", pricePrecision, ", Amount precision:", amountPrecision);
            Log("==============================");
            
            // Check if baseAmount meets minimum order quantity
            if (baseAmount < minQty) {
                Log("⚠️ Warning: baseAmount(" + baseAmount + ") less than minQty(" + minQty + "), auto-adjusted", "#FF9900");
                baseAmount = minQty;
            }
            
            // Normalize baseAmount
            baseAmount = NormalizeAmount(baseAmount);
            if (baseAmount === 0) {
                baseAmount = minQty;
            }
            Log("Normalized baseAmount:", baseAmount);
        }
    } catch (e) {
        Log("Failed to get precision, using defaults: " + e.message, "#FF9900");
    }
}

// Initialize profit chart
function InitChart() {
    var chart = {
        __isStock: true,
        tooltip: { xDateFormat: '%Y-%m-%d %H:%M:%S, %A' },
        title: { text: 'High-Frequency Market Making Strategy Profit Curve' },
        xAxis: { type: 'datetime' },
        yAxis: { title: { text: 'Profit (USDT)' }, opposite: false },
        series: [{ name: 'Profit', data: [] }]
    };
    Chart(chart);
}

// Program entry
function main() {
    LogReset(LOG_RESERVE_COUNT);
    LogProfitReset();
    InitChart();
    
    exchange.SetContractType("swap");
    symbol = exchange.GetCurrency() + ".swap";
    Log("Trading symbol:", symbol);
    
    // Test if Lighter is supported
    if (!exchange.GetAccount()) {
        Log("Account initialization failed, please check if configuration is correct and docker is latest version!")
    }
    
    InitPrecision();
    
    stats.startTime = new Date().getTime();
    stats.lastLogCleanTime = stats.startTime;
    
    var initAccount = _C(exchange.GetAccount);
    var initPos = GetPosition();
    stats.initialEquity = GetEquity(initAccount, initPos);
    stats.maxEquity = stats.initialEquity;
    stats.currentEquity = stats.initialEquity;
    
    Log("========== Strategy Started ==========", "#00FF00");
    Log("Initial Equity:", _N(stats.initialEquity, 4), "USDT");
    Log("Stop Loss Threshold:", _N(stats.initialEquity * stopLossRatio, 4), "USDT", "(" + (stopLossRatio * 100) + "%)");
    Log("Max Position:", maxPosition);
    Log("Close On Exit:", closeOnExit ? "Yes" : "No");
    Log("Initial Account:", initAccount);
    Log("Initial Position:", _C(exchange.GetPositions, symbol));
    
    while (true) {
        try {
            onTick();
        } catch (e) {
            Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message, "#FF0000");
        }
        Sleep(sleeptime);
    }
}

// Exit cleanup
function onexit() {
    Log("========== Strategy Stopped ==========", "#FF0000");
    
    if (closeOnExit) {
        Log("closeOnExit=true, executing exit close all...", "#FF9900");
        CloseAllPositions("Strategy exit");
    } else {
        Log("closeOnExit=false, keeping current positions and orders", "#0000FF");
    }
    
    Log("----------------------------------------");
    Log("Total cycles: " + stats.cycleCount + ", Orders: " + stats.orderCount + ", Cancels: " + stats.cancelCount);
    Log("Initial Equity: " + _N(stats.initialEquity, 4) + " USDT");
    Log("Final Equity: " + _N(stats.currentEquity, 4) + " USDT");
    Log("Total Profit: " + _N(stats.currentEquity - stats.initialEquity, 4) + " USDT");
    Log("Return Rate: " + _N((stats.currentEquity - stats.initialEquity) / stats.initialEquity * 100, 2) + " %");
    Log("Max Drawdown: " + _N(stats.maxDrawdown, 2) + " %");
    Log("----------------------------------------");
}

You tell AI the parameter editing rules on FMZ, and AI can even configure the interface parameters for you. For easier explanation, I manually configured the strategy interface parameters, as shown above.

4. Live Trading

The screenshots are for reference only; the original images are all in Chinese.

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

Zero-Fee Revival of Ancient Strategies: FMZ + Lighter DEX + AI in Practice

Honestly, the code AI writes is really much better than what I write, and it’s rarely wrong (as long as requirements are described clearly, I only iterated 3-4 times before it ran stably). Instantly felt my productivity explode 💥 Although currently it seems like the strategy isn’t making money 😂, but the trading volume has accumulated quite a bit.

5. Summary

The Chemical Reaction of FMZ + AI + Lighter

Looking back at this practice, I have several thoughts:

Zero fees really is a game changer

Previously, many “theoretically beautiful” strategies were defeated by fees—this “invisible killer.” Lighter’s zero-fee policy gives these strategies a chance at rebirth. If you also have similar “shelved strategies,” why not dig them out and try them.

AI greatly lowers the barrier to strategy development

This time I barely wrote any code myself; the entire strategy refactoring was done by AI. This was unimaginable before. For friends who have trading ideas but limited programming ability, the AI + FMZ combination is undoubtedly a blessing.

The value of FMZ’s rapid integration of new exchanges

FMZ’s ability to quickly integrate emerging exchanges like Lighter allows users to seize opportunities immediately. The unified API wrapper also means your strategies can be easily migrated to new platforms.

Next Steps

Next, I plan to:

  • Continue optimizing this strategy’s parameters
  • Try more strategy types suitable for zero-fee environments
  • Explore strategies applicable to Lighter
  • Other ancient strategies, including: OKCoin Leeks Reaper, etc.

Thank You for Your Support

Thank you for reading. If you have good requirements or suggestions, feel free to share them.

6. Disclaimer

This article is for technical exchange and learning reference only and does not constitute any investment advice.

Strategy Risk: The strategy code shown in this article is for technical demonstration only and does not guarantee profitability. Quantitative trading carries inherent risks, and historical backtests or short-term live performance do not represent future returns. Please use cautiously after fully understanding the strategy logic.

DEX Risk: Decentralized exchanges (DEXs) involve smart contract risks, liquidity risks, and network congestion risks. As an emerging platform, Lighter DEX’s long-term stability and security are yet to be verified by the market.

Zero-Fee Policy: Exchange fee policies may be adjusted at any time. Please refer to Lighter’s official announcements for the latest information. Even if trading is fee-free, on-chain gas fees may still apply.

AI-Generated Code: The strategy code in this article was generated with AI assistance. Although tested, potential bugs or logic flaws cannot be ruled out. This is for learning and research only.

Self-Assumed Risk: Any consequences arising from using the information, code, or strategies provided in this article are borne by the user. The author and FMZ platform are not responsible for any direct or indirect losses.

Cryptocurrency trading carries high risks. Please ensure proper risk management and trade rationally.

相关推荐