In the previous article, we introduced cross-exchange “brick moving” arbitrage. In this article, we will take a deep look at how to apply the Lead-Lag effect to high-frequency trading, which requires capturing small price differences in a very short time and making profits quickly. The Lead-Lag effect provides traders with predictive information to help them judge the short-term trend of prices and achieve arbitrage between different exchanges.
The following is a simplification of the public code and converted to FMZ API. The code principle of this original strategy is very simple and was once very profitable. It is currently unavailable and is for reference only.
The so-called “Lead-Lag” can be understood as the prices (or certain indicators) of certain exchanges will be more “leading” in the overall market changes, while other exchanges or other indicators will be relatively “lagging”. In this strategy, “Price_1, Price_2, Price_3” represent the market conditions of different exchanges respectively. They are mainstream exchanges. Because they are more sensitive to market news, or their trading depth and participant types are different, once there are large buy or sell orders, the prices of these exchanges will fluctuate first. The actual trading exchanges will lag slightly in price fluctuations due to factors such as matching mechanisms and trading groups. At this time, the state of “some exchanges lead, some lag” appears.
The strategy obtains order book data from different exchanges almost synchronously, such as the bid price, ask price, pending order volume, etc. Then the middle price (i.e. the average of the bid and ask price) of different exchanges is compared to infer market dynamics.
The strategy mainly focuses on the price changes of three external exchanges (okex, binance, huobipro):
Here, each trendX is determined by the difference between the “current price” and the “past price” exceeding a certain threshold (level * price_increment). After adding the “up/down” signals from the three exchanges, if the overall trend > 0, it means that the market is generally rising, and the strategy is to buy; if trend < 0, it means that the market is generally falling, and the strategy is to sell.
The strategy only buys or sells after the trend is confirmed, and cancels the previous order before placing each order (i.e., avoiding accidental pending orders that lead to risk accumulation). At the same time, the script also sets up modules such as leverage, batch operation, and risk control monitoring, which means that multiple accounts and multiple currency pairs are used simultaneously in the live trading, thereby expanding the strategy’s “trading frequency” and “capital utilization efficiency.”
In addition, this strategy is a high-frequency strategy. You do not need to pay attention to the profit or loss of each order, nor do you need to stop loss. You can continue as long as there is a probability of making profits.
// Hyperparameter settings
const SYMBOL = "BTC_USDT"; // Trading pair
const PRICE_INCREMENT = 0.1; // Price increment
const LEVEL = 10; // Sensitivity of trend judgment
const RATIO = 10; // Order price adjustment ratio
const INTERVAL = 200; // Time interval (milliseconds)
const S_AMOUNT = 0.02; // Default transaction volume
const MIN_AMOUNT = 0.005; // Minimum transaction volume
// Initial state
let buyOrders = [];
let sellOrders = [];
let previousPrices = [0, 0, 0]; // Store the previous price
let loop = 0;
// Get order book data
function fetchOrderBooks() {
let orderBooks = [];
let tasks = [];
// Start asynchronous order book acquisition tasks for all exchanges
for (let i = 0; i < exchanges.length; i++) {
// Assume that each exchange object can call the Go method
let task = exchanges[i].Go("GetDepth");
tasks.push({ index: i, task: task });
}
// Wait for all tasks to complete and collect results
for (let i = 0; i < tasks.length; i++) {
let { index, task } = tasks[i];
try {
// Waiting for an asynchronous task to return a result
let depth = task.wait(1000);
// Check if the returned data is valid
if (!depth || !depth.Bids || !depth.Asks) {
throw new Error("The returned order book data is invalid");
}
// Add valid order book data to the result array
orderBooks[index] = depth;
} catch (error) {
// Recording error logs
Log(`Failed to obtain the order book of exchange ${index}: ${error.message}`);
// Added default order book data to avoid crashes
orderBooks[index] = {
Bids: [[0, 0]],
Asks: [[0, 0]]
};
}
}
return orderBooks;
}
// Judge the trends
function calculateTrend(orderBooks) {
let trends = [];
for (let i = 0; i < orderBooks.length; i++) {
const midPrice = (orderBooks[i].Bids[0][0] + orderBooks[i].Asks[0][0]) / 2;
if (midPrice > previousPrices[i] + LEVEL * PRICE_INCREMENT) {
trends.push(1); // Upward trend
} else if (midPrice < previousPrices[i] - LEVEL * PRICE_INCREMENT) {
trends.push(-1); // Downward trend
} else {
trends.push(0); // No significant trend
}
previousPrices[i] = midPrice; // Update price record
}
return trends.reduce((a, b) => a + b, 0); // Return to overall trend
}
// Cancel all pending orders
function cancelOrders(orders) {
for (let orderId of orders) {
try {
exchanges[0].CancelOrder(orderId); // Use the main exchange by default
Log(`Cancel order: ${orderId}`);
} catch (error) {
Log(`Failed to cancel order: ${error.message}`);
}
}
}
// Create a buy order
function createBuyOrder(price, amount) {
try {
const orderId = exchanges[0].Buy(price, amount);
buyOrders.push(orderId);
Log(`Create a buy order: price ${price}, quantity ${amount}`);
} catch (error) {
Log(`Failed to create buy order: ${error.message}`);
}
}
// Create a sell order
function createSellOrder(price, amount) {
try {
const orderId = exchanges[0].Sell(price, amount);
sellOrders.push(orderId);
Log(`Create a sell order: price ${price}, quantity ${amount}`);
} catch (error) {
Log(`Failed to create sell order: ${error.message}`);
}
}
function main() {
while (true) {
try {
// Get order book data
const orderBooks = fetchOrderBooks();
// Calculate trends
const trend = calculateTrend(orderBooks);
Log(`Current trend: ${trend}`);
// Cancel pending order
cancelOrders(buyOrders);
cancelOrders(sellOrders);
buyOrders = [];
sellOrders = [];
// Order based on trends
if (trend > 0 && loop > 0) {
const price = _N(orderBooks[0].Bids[0][0] + RATIO * PRICE_INCREMENT, 2);
const amount = _N(Math.max(S_AMOUNT, MIN_AMOUNT), 4);
createBuyOrder(price, amount);
} else if (trend < 0 && loop > 0) {
const price = _N(orderBooks[0].Asks[0][0] - RATIO * PRICE_INCREMENT, 2);
const amount = _N(Math.max(S_AMOUNT, MIN_AMOUNT), 4);
createSellOrder(price, amount);
}
// Loop count and interval
loop++;
Sleep(INTERVAL);
} catch (error) {
Log(`Main logic error: ${error.message}`);
}
}
}
Markets become efficient
When more and more quantitative or high-frequency strategies are involved and find the same Lead-Lag relationship, a large amount of funds will eliminate the price difference quickly. The market becomes more and more “synchronized”, and it is difficult for strategies to make “risk-free” arbitrage or short-term arbitrage from small price differences.
Exchange restrictions or fee changes
As the fee structure of different exchanges changes, once the fee costs exceed the arbitrage profits, the profitability of high-frequency trading strategies will be reduced greatly. Or if the exchange speeds up the matching speed, limits the frequency and quantity, and reduces the delay, it will also invalidate the strategy that originally relied on inconsistent delays.
Liquidity decay and slippage
When market volume is insufficient, high-frequency strategies often encounter more severe slippage; or large orders will push up prices quickly, causing the originally expected “buy low and sell high” to be affected by their own orders, resulting in a decline in returns.
Changes in market volatility
Some strategies work very well under “high volatility” or “specific period”. When the market is flat or volatility decreases and leverage is reduced, the strategy loses its suitable environment and may even incur frequent losses.
The key point of the high-frequency trading strategy lies in the capture of prices from multiple exchanges and the judgment of “trend synthesis”. It once realized an ultra-high frequency and fast entry and exit trading method based on the Lead-Lag principle: observe which exchange’s price moves first, and then drive other exchanges’ prices to follow, thereby capturing instantaneous price differences or short-term trends. However, as the author said, changes in market environment, strategy homogeneity, handling fees and frequency limits have made this strategy that relies on the “first move and then move” price difference become less useful gradually, and even lose profitability. For those who want to explore this type of Lead-Lag strategy again, it is necessary to optimize the trading module in combination with the latest market structure (liquidity, handling fee rules, algorithm matching speed), while paying attention to risk control management, in order to maintain continuous competitiveness in a changing market environment.