High-frequency trading is a challenging and competitive field that relies on rapid trade execution and sensitive insights into the microstructure of the market. One notable strategy is Penny Jump, which focuses on exploiting "elephants" in the market to gain small but frequent profits. In this article, we will explain in detail how the Penny Jump strategy works, delving into the details of its code, so that beginners can understand how it operates.
Understanding the Penny Jump Strategy
In the stock market, "elephants" usually refer to those institutional investors who wish to buy or sell a large number of shares but are unwilling to trade at market price. Instead, they choose to hang a large number of limit orders in the market, i.e., pending orders, to indicate their intentions. This behavior has attracted widespread attention in the market, because large transactions may have a significant impact on the market.
For example, suppose the original depth of a stock's market was like this: 200 | $1.01 x $1.03 | 200. Then an "elephant" enters and places a buy order for 3000 shares at $1.01 each. At this point, the depth of the market will change to 3,200 | $1.01 x $1.03 | 200 . This action is like introducing an "elephant", which becomes the focus of other participants in the marketplace.
Competitive market
For high-frequency traders, their profits mainly come from the analysis of market microstructure to speculate on the intentions of other traders. Once a big player appears, high-frequency traders will establish positions quickly to capture minor price fluctuations. Their goal is to trade frequently in a short period of time and accumulate small but cumulative profits.The Dilemma of the Elephant
Even though elephants might wish to operate on a large scale in the market, their actions also reveal their trading intentions, making them targets for high-frequency traders. High-frequency traders attempt to establish positions ahead of time and then profit from price fluctuations. The presence of elephants in the market could trigger reactions in competitive markets, thereby affecting their trading strategies.Deception in the Market
In reality, large institutional investors usually do not place a large number of buy or sell orders in the market blatantly, as such behavior could lead other participants in the market to take countermeasures or even manipulate the market. Therefore, they may adopt strategies to create illusions, attract high-frequency traders into the field, and then quickly sell or buy to profit from price fluctuations.
The Core Idea of the Penny Jump Strategy
The core idea of the Penny Jump strategy is that once a "big player" appears in the market and supports a specific price (such as $1.01), high-frequency traders will quickly raise their bid by one cent, for instance, to $1.02. This is because high-frequency traders understand that the appearance of a big player means there's strong buying support at this price level, so they try to follow closely in hopes of a price increase. When the price indeed rises to $1.03 x $1.05, high-frequency traders can sell quickly and earn a profit of $0.01.
Not only that, but high-frequency traders can also make profits after purchasing even if the price doesn't rise, because they know that the big player has supported the base price; hence they can swiftly sell their stocks to this big player and gain tiny arbitrage profits.
Analyzing Penny Jump Strategy Code
Strategy source code: https://www.fmz.com/strategy/358
The strategy code provided above is an example, used to implement the Penny Jump strategy. Below is a detailed explanation of the code, enabling beginners to understand how it works:
var Counter = {
i: 0,
w: 0,
f: 0
};
// Variables
var InitAccount = null;
function CancelAll() {
while (true) {
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
}
Sleep(Interval);
}
}
function updateStatus(msg) {
LogStatus("Number of debugging sessions:", Counter.i, "succeeded:", Counter.w, "failed:", Counter.f, "\n"+msg+"#0000ff\n"+new Date());
}
function main() {
if (DisableLog) {
EnableLog(false);
}
CancelAll();
InitAccount = _C(exchange.GetAccount);
Log(InitAccount);
var i = 0;
var locks = 0;
while (true) {
Sleep(Interval);
var depth = _C(exchange.GetDepth);
if (depth.Asks.length === 0 || depth.Bids.length === 0) {
continue;
}
updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);
var askPrice = 0;
for (i = 0; i < depth.Asks.length; i++) {
if (depth.Asks[i].Amount >= Lot) {
askPrice = depth.Asks[i].Price;
break;
}
}
if (askPrice === 0) {
continue;
}
var elephant = null;
// skip Bids[0]
for (i = 1; i < depth.Bids.length; i++) {
if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {
break;
}
if (depth.Bids[i].Amount >= ElephantAmount) {
elephant = depth.Bids[i];
break;
}
}
if (!elephant) {
locks = 0;
continue;
}
locks++;
if (locks < LockCount) {
continue;
}
locks = 0;
updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));
exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);
var ts = new Date().getTime();
while (true) {
Sleep(CheckInterval);
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
if ((new Date().getTime() - ts) > WaitInterval) {
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
}
}
}
var account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
Counter.f++;
Counter.i++;
continue;
}
updateStatus("Successful payment: " + opAmount +", Start taking action...");
exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);
var success = true;
while (true) {
var depth = _C(exchange.GetDepth);
if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price-(STTick*PennyTick))) {
success = false;
updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);
CancelAll();
account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
break;
}
exchange.Sell(depth.Bids[0].Price, opAmount);
}
var orders = _C(exchange.GetOrders);
if (orders.length === 0) {
break;
}
Sleep(CheckInterval);
}
if (success) {
Counter.w++;
} else {
Counter.f++;
}
Counter.i++;
var account = _C(exchange.GetAccount);
LogProfit(account.Balance - InitAccount.Balance, account);
}
}
I will parse your provided strategy code line by line to help you understand its operation in detail.
var Counter = {
i: 0,
w: 0,
f: 0
};
This code initializes an object named Counter, which is used to track the trading statistical information of a strategy. Specifically, it includes three attributes:
i: Represents the total number of transactions.
w: Represents the number of successful transactions.
f: Represents the number of failed transactions.
These attributes will be recorded and updated during the strategy execution process.
var InitAccount = null;
This line of code initializes a variable named InitAccount, which will store account information when the strategy starts executing.
function CancelAll() {
while (true) {
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
}
Sleep(Interval);
}
}
This is a function named CancelAll()
, its purpose is to cancel all unfulfilled orders in the market. Let's explain its functions step by step:
while (true)
: This is an infinite loop, it will continue to run until there are no uncompleted orders.var orders = _C(exchange.GetOrders)
: This line of code uses the exchange.GetOrders function to retrieve all pending orders in the current account and stores them in the orders variable.if (orders.length == 0)
: This line of code checks for any unfinished orders. If the length of the orders array is 0, it means there are no unfinished orders and the loop will be interrupted (break).for (var i = 0; i < orders.length; i++)
: This is a for loop that iterates through all uncompleted orders.exchange.CancelOrder(orders[i].Id)
: This line of code uses the exchange.CancelOrder() function to cancel each order by its ID.Sleep(Interval)
: This line of code introduces a waiting period, pausing for a certain amount of time (in milliseconds), to ensure that the operation of cancelling orders is not too frequent.
This line of code introduces a waiting period, pausing for a certain amount of time (in milliseconds), to ensure that the operation of cancelling orders is not too frequent.
function updateStatus(msg) {
LogStatus("Number of debugging sessions:", Counter.i, "succeeded:", Counter.w, "failed:", Counter.f, "\n" + msg + "#0000ff\n" + new Date());
}
This is a function named updateStatus(msg)
, which is used to update and record transaction status information. It accepts a msg parameter, which usually contains information about the current market status. The specific operations of the function include:
Using the LogStatus()
function to record the information displayed in the status bar during strategy execution. It displays text about trade counts, successful counts, and failure counts.
The msg
parameter is appended, which contains information about the current market status.
The current timestamp (new Date()
) is appended to display time information.
The purpose of this function is to record and update transaction status information for monitoring and analysis during strategy execution.
function main() {
if (DisableLog) {
EnableLog(false);
}
CancelAll();
InitAccount = _C(exchange.GetAccount);
Log(InitAccount);
var i = 0;
var locks = 0;
while (true) {
Sleep(Interval);
var depth = _C(exchange.GetDepth);
if (depth.Asks.length === 0 || depth.Bids.length === 0) {
continue;
}
updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);
var askPrice = 0;
for (i = 0; i < depth.Asks.length; i++) {
if (depth.Asks[i].Amount >= Lot) {
askPrice = depth.Asks[i].Price;
break;
}
}
if (askPrice === 0) {
continue;
}
var elephant = null;
// skip Bids[0]
for (i = 1; i < depth.Bids.length; i++) {
if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {
break;
}
if (depth.Bids[i].Amount >= ElephantAmount) {
elephant = depth.Bids[i];
break;
}
}
if (!elephant) {
locks = 0;
continue;
}
locks++;
if (locks < LockCount) {
continue;
}
locks = 0;
updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));
exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);
var ts = new Date().getTime();
while (true) {
Sleep(CheckInterval);
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
if ((new Date().getTime() - ts) > WaitInterval) {
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
}
}
}
var account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
Counter.f++;
Counter.i++;
continue;
}
updateStatus("Successful payment: " + opAmount +", Start taking action...");
exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);
var success = true;
while (true) {
var depth = _C(exchange.GetDepth);
if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price-(STTick*PennyTick))) {
success = false;
updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);
CancelAll();
account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
break;
}
exchange.Sell(depth.Bids[0].Price, opAmount);
}
var orders = _C(exchange.GetOrders);
if (orders.length === 0) {
break;
}
Sleep(CheckInterval);
}
if (success) {
Counter.w++;
} else {
Counter.f++;
}
Counter.i++;
var account = _C(exchange.GetAccount);
LogProfit(account.Balance - InitAccount.Balance, account);
}
}
This is the main execution function main()
of the strategy, which contains the core logic of the strategy. Let's explain its operations line by line:
if (DisableLog)
: This line of code checks if the DisableLog variable is true, and if so, it will disable log recording. This is to ensure that unnecessary logs are not recorded by the strategy.CancelAll()
: Call the previously explained CancelAll() function to ensure that there are no unfinished orders.InitAccount = _C(exchange.GetAccount)
: This line of code retrieves the current account information and stores it in the InitAccount variable. This will be used to record the account status when the strategy starts executing.var i = 0;
andvar locks = 0;
: Initialize two variables, i and locks, which will be used in the subsequent strategy logic.while (true)
: This is an infinite loop, mainly used for the continuous execution of strategies.
Next, we will explain the main strategy logic within the while (true)
loop line by line.
while (true) {
Sleep(Interval);
var depth = _C(exchange.GetDepth);
if (depth.Asks.length === 0 || depth.Bids.length === 0) {
continue;
}
updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);
Sleep(Interval)
: This line of code allows the strategy to sleep for a period of time, in order to control the execution frequency of the strategy. The Interval parameter defines the sleep interval (in milliseconds).var depth = _C(exchange.GetDepth)
: Obtain the current market depth information, including the prices and quantities of sell orders and buy orders. This information will be stored in the depth variable.if (depth.Asks.length === 0 || depth.Bids.length === 0)
: This line of code checks the market depth information, ensuring that both sell orders and buy orders exist. If one of them does not exist, it indicates that the market may not have enough trading information, so the strategy will continue to wait.updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks)
: This line of code calls the updateStatus function to update the status information of the strategy. It records the current market status, including the highest bid price, lowest ask price and previously locked times (locks).
var askPrice = 0;
for (i = 0; i < depth.Asks.length; i++) {
if (depth.Asks[i].Amount >= Lot) {
askPrice = depth.Asks[i].Price;
break;
}
}
if (askPrice === 0) {
continue;
}
var elephant = null;
var askPrice = 0;
: Initialize the askPrice variable, it will be used to store the price of sell orders that meet the conditions.for (i = 0; i < depth.Asks.length; i++)
: This is a for loop used to traverse the price and quantity information of market sell orders.if (depth.Asks[i].Amount >= Lot)
: In the loop, check if the quantity of each sell order is greater than or equal to the specified Lot (hand count). If so, store the price of that sell order in askPrice and terminate the loop.if (askPrice === 0)
: If no sell orders that meet the conditions are found (askPrice is still 0), the strategy will continue to wait and skip subsequent operations.var elephant = null;
: Initialize the elephant variable, it will be used to store the buy order information identified as "elephant".
for (i = 1; i < depth.Bids.length; i++) {
if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {
break;
}
if (depth.Bids[i].Amount >= ElephantAmount) {
elephant = depth.Bids[i];
break;
}
}
if (!elephant) {
locks = 0;
continue;
}
locks++;
if (locks < LockCount) {
continue;
}
locks = 0;
Continue to traverse the price and quantity information of market buy orders, skipping the first buy order (Bids[0]).
if ((askPrice - depth.Bids[i].Price) > ElephantSpace)
: Check whether the gap between the current bid price and askPrice is greater than ElephantSpace. If so, it indicates that it is far enough from the "elephant", and the strategy will no longer continue to search.if (depth.Bids[i].Amount >= ElephantAmount)
: Check if the quantity of the current buy order is greater than or equal to ElephantAmount. If so, store the buy order information in the elephant variable.if (!elephant)
: If the "elephant" is not found, reset the lock count to 0 and continue waiting.locks++
: If the "elephant" is found, increment the lock count. This is to ensure that the strategy is executed only after confirming the existence of the "elephant" multiple times over a period of time.if (locks < LockCount)
: Check whether the number of lock times has met the requirement (LockCount). If it hasn't, continue to wait.
updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));
exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);
var ts = new Date().getTime();
while (true) {
Sleep(CheckInterval);
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
if ((new Date().getTime() - ts) > WaitInterval) {
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
}
}
}
updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant))
: Call the updateStatus function to record the current status of the strategy, including the gear position of the "elephant" found and related information. This will be displayed in the status bar of the strategy.exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant)
: Use the exchange.Buy function to purchase the found "elephant". The purchase price is elephant.Price + PennyTick, the purchase quantity is Lot, and describe the purchase operation as "Bids[" + i + "]".var ts = new Date().getTime()
: Obtain the timestamp of the current time for subsequent calculation of time intervals.while (true)
: Enter a new infinite loop, used to wait for the execution of "elephant" buy orders.Sleep(CheckInterval)
: The strategy sleeps for a while to control the frequency of checking order status.var orders = _C(exchange.GetOrders)
: Obtain all order information of the current account.if (orders.length == 0)
: Check if there are any unfinished orders, if not, break the loop.(new Date().getTime() - ts) > WaitInterval
: Calculate the time interval between the current time and when the "elephant" was purchased. If it exceeds WaitInterval, it means that the waiting has timed out.for (var i = 0; i < orders.length; i++)
: Traverse through all uncompleted orders.exchange.CancelOrder(orders[i].Id)
: Use the exchange.CancelOrder function to cancel each unfinished order.
var account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
Counter.f++;
Counter.i++;
continue;
}
updateStatus("Successful payment: " + opAmount + ", Start taking action...");
exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);
var success = true;
while (true) {
var depth = _C(exchange.GetDepth);
if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price - (STTick * PennyTick))) {
success = false;
updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);
CancelAll();
account = _C(exchange.GetAccount);
var opAmount = _N(account.Stocks - InitAccount.Stocks);
if (opAmount < 0.001) {
break;
}
exchange.Sell(depth.Bids[0].Price, opAmount);
}
var orders = _C(exchange.GetOrders);
if (orders.length === 0) {
break;
}
Sleep(CheckInterval);
}
if (success) {
Counter.w++;
} else {
Counter.f++;
}
Counter.i++;
var account = _C(exchange.GetAccount);
LogProfit(account.Balance - InitAccount.Balance, account);
}
var account = _C(exchange.GetAccount)
: Obtain current account information.var opAmount = _N(account.Stocks - InitAccount.Stocks)
: Calculate the change in account assets after purchasing the "elephant". If the change is less than 0.001, it indicates that the purchase has failed, increase the number of failures and continue to the next loop.updateStatus("Successful payment: " + opAmount + ", Start taking action...")
: Record the successful purchase information of "elephant", including the quantity purchased.exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount)
: Use the exchange.Sell function to sell the successfully purchased "elephant" for profit. The selling price is elephant.Price + (PennyTick * ProfitTick).
Enter a new infinite loop, used to wait for the execution of sell orders.
var depth = _C(exchange.GetDepth)
: Obtain market depth information.if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price - (STTick * PennyTick)))
: Check the market depth information, if the market price has already fallen to the stop-loss level, then execute the stop-loss operation.CancelAll()
: Call the CancelAll() function to cancel all uncompleted orders, in order to avoid position risk.if (opAmount < 0.001)
: Check the purchase quantity again, if it's less than 0.001, it indicates that the purchase has failed, break out of the loop.exchange.Sell(depth.Bids[0].Price, opAmount)
: Execute a stop-loss operation, sell the remaining assets at the current market's lowest price.
Finally, update the number of successful and failed transactions based on whether the transaction was successful or not, and record the trading profits.
This is a line-by-line explanation of the entire strategy. The core idea of this strategy is to find "elephants" (large buy orders) in the market, buy and sell them to gain small profits. It includes several important parameters, such as Lot, error retry interval (Interval), ElephantAmount, ElephantSpace, etc., to adjust the strategy.
In general, this strategy is a high-frequency trading strategy aimed at utilizing market depth information to identify large buy orders and carry out buying and selling transactions in a short period. It needs constant monitoring of the market and execution of buying and selling operations to quickly gain small profits. However, it's also a high-risk strategy, because it requires quick responses to market fluctuations while considering risk management and stop-loss mechanisms to avoid significant losses.
Please note that the strategy is based on specific markets and trading platforms. For different markets and exchanges, appropriate adjustments and optimizations may be needed. In practical application, investors need to carefully test and evaluate the performance of the strategy to ensure it aligns with their investment goals and risk tolerance.
As you continue to execute the strategy, it will repeatedly perform the following operations:
Firstly, the strategy will check the depth information of the market to understand the current situation of sell orders and buy orders.
Next, the strategy will attempt to find sell orders that meet the criteria, specifically sell orders with a quantity greater than or equal to Lot. If a qualifying sell order is found, the price of the sell order will be recorded as askPrice.
Then, the strategy will continue to search for "elephants" (large amount of buy orders). It will traverse through the market's buy orders, skipping the first one (usually the highest-priced buy order). If it finds an "elephant" that meets the criteria, it will record information about the "elephant", and increase locks.
If a sufficient number of "elephants" are found consecutively (controlled by the LockCount parameter), the strategy will further perform the following operations:
Call the updateStatus function to record the gear and related information of the "elephant".
Use the exchange.Buy function to purchase an "elephant", with a purchase price of elephant.Price + PennyTick, and a quantity of Lot.
Start a new infinite loop for waiting for execution of the buy order.
Check order status. If it is completed, break out from loop.
If waiting time exceeds set interval (WaitInterval), cancel all uncompleted orders.
Calculate changes in account assets after successful purchase. If change is less than 0.001, it indicates that purchase failed; increase failure count and continue next loop.
Record information about successful purchases of "elephants", including quantity purchased.
- Next, the strategy will continue to enter a new infinite loop, waiting for the execution of sell operations. In this loop, it will perform the following actions:
Obtain market depth information, check if the market price has already reached the stop-loss level.
If the market price has reached or fallen below the stop-loss level, a stop-loss operation will be executed, that is, the remaining assets will be sold.
Call the CancelAll function to cancel all uncompleted orders, reducing position risk.
Recheck the change in account assets after a successful purchase. If the change is less than 0.001, it indicates that the purchase has failed and exit the loop.
Finally, record whether the transaction is successful or not, and update the number of successes and failures based on the transaction results.
The entire strategy continuously carries out the above operations to capture as many "elephants" as possible and obtain small profits. This is a high-frequency trading strategy that requires quick responses to market changes, while also considering risk management and stop-loss mechanisms to protect capital. Investors should carefully consider using this strategy, especially in highly volatile markets.
Summary
The Penny Jump strategy is a typical example in high-frequency trading, demonstrating the subtle game and competition among market participants. This strategy is particularly prominent in the cryptocurrency market due to its large fluctuations, where institutional investors and high-frequency traders are all pursuing quick profits. However, this also makes the market full of challenges, requiring constant adaptation and adjustment of strategies to maintain competitive advantages. In this fiercely competitive world, only those traders who are good at discerning the microstructure of the market and responding quickly can achieve success.
From: https://blog.mathquant.com/2023/11/07/high-frequency-trading-strategy-analysis-penny-jump.html