Detailed Explanation of Futures Backhand Doubling Algorithm Strategy Notes
This strategy is a strategy that was applied to Digital Currency 796 Futures Exchange long time ago. Futures contracts are currency-based, that is, the margin is deducted by currency (for example, BTC contracts are deducted by BTC). The order quantity of contracts can be decimal, similar to currency-based contracts of Binance.
The strategy is used for learning strategy design, the logic processing is still very good, and the strategy is mainly used for learning.
Strategy code notes
pinevar FirstTradeType = [ORDER_TYPE_BUY, ORDER_TYPE_SELL][OpType]; // The first opening position direction is determined according to the parameter OpType opening position direction. The value of the global variable FirstTradeType is ORDER_ TYPE_ BUY or ORDER_ TYPE_ SELL
var OrgAccount = null; // Global variable, recording account assets
var Counter = {s : 0, f: 0}; // Declare a variable Counter, the value is initialized to an object (a similar structure python named dictionary), s represents the number of win time, f represents the number of losses
var LastProfit = 0; // Recent P&L
var AllProfit = 0; // Total P&L
var _Failed = 0; // Number of stop-losses
// Cancel all orders other than those with an ID in the list of pending orders. If the orderId parameter was not passed in, cancel all pending orders of the current trading pair. The parameter e is the reference of the exchange object. For example, when exchange is passed in as a parameter, e is the alias of exchange
function StripOrders(e, orderId) {
var order = null; // Initialize the variable order to null
if (typeof(orderId) == 'undefined') { // If the parameter orderId is not written when it is passed in, then typeof (orderId)=='undefined' holds, the code block of the if statement is executed, and the orderId is assigned to null
orderId = null;
}
while (true) { // Process loop
var dropped = 0; // Process number of markers
var orders = _C(e.GetOrders); // Call GetOrders to get the current pending orders (orders that are not dealt) and assign the value to orders
for (var i = 0; i < orders.length; i++) { // Traverse the list of outstanding orders
if (orders[i].Id == orderId) { // If the order ID is the same as the order ID passed in the parameter, assign the value orders[i] to the local variable order in the function. Orders [i] refers to the current order structure when traversing
order = orders[i];
} else { // If the IDs are not the same, perform revocation
var extra = ""; // Set the extension information extra according to the partial deals
if (orders[i].DealAmount > 0) {
extra = "Deal:" + orders[i].DealAmount;
} else {
extra = "Unsettled";
}
e.SetDirection(orders[i].Type == ORDER_TYPE_BUY ? "buy" : "sell");
e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "purchase order" : "selling order", extra); // Cancel the order, with extra information output, which will be displayed in the log
dropped++; // dropped count accumulation
}
}
if (dropped == 0) { // When the traversal is completed, dropped is equal to 0, that is, there is no cancellation during traversal (there is no order to be canceled), that is, the cancellation processing is completed, and the while loop is skipped
break;
}
Sleep(300); // Prevent the rotation frequency from being too fast, with a certain interval each time
}
return order; // Return the order to find
}
var preMsg = ""; // Variables for recording cache information
function GetAccount(e, waitFrozen) { // Obtain the account asset information. The parameter e is also a reference to exchange. The parameter waitFrozen controls whether to wait for freezing
if (typeof(waitFrozen) == 'undefined') { // If the waitFrozen parameter is not passed in during the call, the parameter waitFrozen is assigned a value of false, that is, the default is not to wait for freezing
waitFrozen = false;
}
var account = null;
var alreadyAlert = false; // Mark if the variable has been alerted
while (true) { // Get the current account information and detect freezing. If you do not wait for freezing, it will skip the while loop directly
account = _C(e.GetAccount);
if (!waitFrozen || account.FrozenStocks < MinStock) {
break;
}
if (!alreadyAlert) {
alreadyAlert = true; // Once the reminder is triggered, the alreadyAlert will be reset to avoid repeated reminders
Log("find frozen money or currencies in the account", account); // Output reminder log
}
Sleep(Interval);
}
msg = "successful:" + Counter.s + "times, failed:" + Counter.f + "times, current account currencies: " + account.Stocks;
if (account.FrozenStocks > 0) {
msg += "frozen currencies:" + account.FrozenStocks;
}
if (msg != preMsg) { // Check if the current message is different from the last one, and update it on the status bar if it is different
preMsg = msg;
LogStatus(msg, "#ff0000");
}
return account; // Function returns to account structure of the account information
}
function GetPosition(e, orderType) { // Get the position, or get the position in the specified direction
var positions = _C(e.GetPosition); // Get the position
if (typeof(orderType) == 'undefined') { // The orderType parameter is the position type speicfied to be obtained. If no orderType parameter is passed in, all positions will be returned directly
return positions;
}
for (var i = 0; i < positions.length; i++) { // Traverse the list of positions
if (positions[i].Type == orderType) { // If the current traversed position data is the direction to be found (orderType)
return positions[i]; // Return to the position of orderType
}
}
return null;
}
function GetTicker(e) { // Get ticker market data
while (true) {
var ticker = _C(e.GetTicker); // Get ticker market information
if (ticker.Buy > 0 && ticker.Sell > 0 && ticker.Sell > ticker.Buy) { // Check the reliability of the market data
return ticker; // Return to ticker data
}
Sleep(100);
}
}
// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxSpace, retryDelay) { // Trading functions
// e Exchange object reference, trading direction of tradeType (buy/sell), trading volume of tradeAmount, the trading mode is mode, slidePrice means slide price, maxSpace means maximum distance of pending order, retryDelay means retry intervals
var initPosition = GetPosition(e, tradeType); // Get the position data of the specified direction, name it as initPosition
var nowPosition = initPosition; // Declare another variable, nowPosition, and assign it with initPosition
var orderId = null;
var prePrice = 0; // The price of placing order at last loop
var dealAmount = 0; // Number of transactions already made
var diffMoney = 0;
var isFirst = true; // The marker of first loop execution
var tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell; // Function for placing orders, and call e.Buy or e.Sell according to the parameter tradeType
var isBuy = tradeType == ORDER_TYPE_BUY; // Marker for buying
while (true) { // while loop
var account = _C(e.GetAccount); // Obtain the current account asset data
var ticker = GetTicker(e); // Obtain the current market data
var tradePrice = 0; // Set transaction prices based on mode parameters
if (isBuy) {
tradePrice = _N((mode == 0 ? ticker.Sell : ticker.Buy) + slidePrice, 4);
} else {
tradePrice = _N((mode == 0 ? ticker.Buy : ticker.Sell) - slidePrice, 4);
}
if (orderId == null) {
if (isFirst) { // Judge according to the isFirst marker variable, if it is the first execution, do nothing
isFirst = false; // Set the isFirst marker as false, indicating that it was not executed for the first time
} else { // Non-first execution, update the position data
nowPosition = GetPosition(e, tradeType);
}
dealAmount = _N((nowPosition ? nowPosition.Amount : 0) - (initPosition ? initPosition.Amount : 0), 6); // Calculate the number of transactions according to the initial position data and current position data
var doAmount = Math.min(tradeAmount - dealAmount, account.Stocks * MarginLevel, 4); // Calculate the remaining amount to be transacted according to the number of transactions and the available assets of the account
if (doAmount < MinStock) { // If the calculated transaction amount is less than the minimum transaction amount, stop the logic and skip out of the while loop
break;
}
prePrice = tradePrice; // Cache the transaction price in the current loop
e.SetDirection(tradeType == ORDER_TYPE_BUY ? "buy" : "sell"); // Set the direction of futures trading
orderId = tradeFunc(tradePrice, doAmount); // Place a transaction, the parameters are the calculated price, the number of orders placed for this time
} else { // If the orderId of the recorded order variable is not null, it indicates that an order has been placed
if (mode == 0 || Math.abs(tradePrice - prePrice) > maxSpace) { // If it is the maker mode, the current price and the last cached price exceed the maximum maker range
orderId = null; // Reset the orderId to a null value, and the order will be placed again in the next loop
}
var order = StripOrders(exchange, orderId); // Call StripOrders to find the order with ID of orderId in the list of pending orders
if (order == null) { // If it cannot be found, reset the orderId to a null value, too, and continue the next round of order placement
orderId = null;
}
}
Sleep(retryDelay); // A certain time is tentatively determined to control the frequency of the loop
}
if (dealAmount <= 0) { // At the end of the while loop, if the amount of dealAmount is less than or equal to 0, it means that the transaction failed to return the null value
return null;
}
return nowPosition; // If it's normal condition, returns to the latest position data
}
function coverFutures(e, orderType) { // Function for closing the position
var coverAmount = 0; // Declare a variable coverAmount with an initial assignment of 0 to record the amount of closed positions
while (true) {
var positions = _C(e.GetPosition); // Obtain the position
var ticker = GetTicker(e); // Obtain the current quotations
var found = 0; // Find the marker
for (var i = 0; i < positions.length; i++) { // Iterate through the array of positions
if (positions[i].Type == orderType) { // Find the position needed
if (coverAmount == 0) {
coverAmount = positions[i].Amount; // Record the position amount at the beginning, that is, the amount to be closed
}
if (positions[i].Type == ORDER_TYPE_BUY) { // Perform the closing position operation according to the position type
e.SetDirection("closebuy"); // Set the futures trading direction
e.Sell(ticker.Buy, positions[i].Amount); // Order function
} else {
e.SetDirection("closesell");
e.Buy(ticker.Sell, positions[i].Amount);
}
found++; // Marker accumulation
}
}
if (found == 0) { // If the marker variable found is 0, then there are no positions to process and the while loop is skipped
break;
}
Sleep(2000); // 2 seconds interval
StripOrders(e); // Cancel all the current pending orders
}
return coverAmount; // Return to the number of closed positions
}
function loop(pos) {
var tradeType = null; // Trading direction of initialization
if (typeof(pos) == 'undefined' || !pos) { // Determine if it is the first round of execution
tradeType = FirstTradeType;
pos = Trade(exchange, tradeType, OpAmount, OpMode, SlidePrice, MaxSpace, Interval); // First transaction
if (!pos) {
throw "Failure to open a position";
} else {
Log(tradeType == ORDER_TYPE_BUY ? "Open long positions to complete" : "Open short positions to complete", "average price:", pos.Price, "amount:", pos.Amount);
}
} else {
tradeType = pos.Type; // Continue to specify the direction of the transaction according to the direction of the position
}
var holdPrice = pos.Price; // Position price
var holdAmount = pos.Amount; // Position amount
var openFunc = tradeType == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell; // Long position, open position to buy, otherwise open position to sell
var coverFunc = tradeType == ORDER_TYPE_BUY ? exchange.Sell : exchange.Buy; // Long position, close position to sell, otherwise close position to buy
var reversePrice = 0; // Backhand price
var coverPrice = 0; // The price of closing the position
var canOpen = true; // Mark of opening position
if (tradeType == ORDER_TYPE_BUY) {
reversePrice = _N(holdPrice * (1 - StopLoss), 4); // Stop loss price
coverPrice = _N(holdPrice * (1 + StopProfit), 4); // Stop profit price
} else {
reversePrice = _N(holdPrice * (1 + StopLoss), 4);
coverPrice = _N(holdPrice * (1 - StopProfit), 4);
}
var coverId = null;
var msg = "Position price" + holdPrice + "stop loss price:" + reversePrice;
for (var i = 0; i < 10; i++) { // It controls up to 10 orders
if (coverId) { // The order ID is empty, and the break is not triggered. Continue the loop until 10 times
break;
}
if (tradeType == ORDER_TYPE_BUY) { // Place an order according to the direction and place a closing position order, i.e. an order to stop profit
exchange.SetDirection("closebuy");
coverId = exchange.Sell(coverPrice, holdAmount, msg);
} else {
exchange.SetDirection("closesell");
coverId = exchange.Buy(coverPrice, holdAmount, msg);
}
Sleep(Interval);
}
if (!coverId) { // Failed to place an order for 10 times and threw an error, the strategy stopped
StripOrders(exchange); // Cancel all the pending orders
Log("Failed to place an order", "@") // Add push alerts
throw "Failed to place an order"; // Throw an error to stop the robot
}
while (true) { // Enter the loop of detecting the backhand
Sleep(Interval);
var ticker = GetTicker(exchange); // Get the latest quotation
if ((tradeType == ORDER_TYPE_BUY && ticker.Last < reversePrice) || (tradeType == ORDER_TYPE_SELL && ticker.Last > reversePrice)) { // Detect trigger stop loss and backhand
StripOrders(exchange); // Cancel all the pending orders
var coverAmount = coverFutures(exchange, tradeType); // Closinng all the positions
if (_Failed >= MaxLoss) { // If the maximum stop loss times (backhand times) are exceeded, skip out of the loop and start again
Counter.f++; // Record a failure
Log("Exceed the maximum number of failures", MaxLoss);
break; // Skip out of the loop
}
var reverseAmount = _N(coverAmount * ReverseRate, 4); // Double the trading volume according to the amount of closed positions
var account = GetAccount(exchange, true); // Update the account information. At this time, no assets can be frozen
// Check whether the account assets are sufficient. If not, skip out of the loop and start again, as _ Failed >= MaxLoss
if (_N(account.Stocks * MarginLevel, 4) < reverseAmount) { // Check whether the assets are sufficient
Log("Open a backhand position if there is no currencies, it needs to be opened: ", reverseAmount, "amount, only", account.Stocks, "currencies");
Counter.f++;
break;
}
var reverseType = tradeType; // Record the reverse operation type. The default is forward position
if (ReverseMode == 0) { // The backhand mode affects the adjustment, that is, if the parameter is set to the reverse position, adjust here
reverseType = tradeType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; // Reverse the position means that if you have a long position just now, you shall go short at this time. If you have a short position just now, you shall go long at this time
}
var pos = Trade(exchange, reverseType, reverseAmount, OpMode, SlidePrice, MaxSpace, Interval); // Double the operation of placing an order
if (pos) { // Detect the positions after trading logic execution
Log(reverseType == ORDER_TYPE_BUY ? "long position" : "short position", "Complete it by doubling the opening position");
}
return pos; // Return to the position structure
} else { // Logic executed when no backhand is triggered
var orders = _C(exchange.GetOrders); // When the stop-profit order is filled, the number of wins recorded will be increased by 1
if (orders.length == 0) {
Counter.s++;
var account = GetAccount(exchange, true); // Update account assets
LogProfit(account.Stocks, account); // Print account assets
break;
}
}
}
// If it is not returned normally in the while loop, return to null. For example, if the profit stop is successful, the number of failures is exceeded, and the asset is insufficient
return null;
}
function onexit() { // When the robot stops, execute the round-off function onexit
StripOrders(exchange); // Cancel all the pending orders
Log("Exit");
}
function main() {
if (exchange.GetName().indexOf("Futures") == -1) { // Check whether the first exchange object currently added is a futures exchange
throw "Only futures are supported, but not spots for now";
}
// EnableLogLocal(SaveLocal);
if (exchange.GetRate() != 1) { // Do not enable exchange rate conversion
Log("Exchange rate conversion disabled");
exchange.SetRate(1);
}
StopProfit /= 100; // The parameter is processed as a decimal number. If StopProfit is 1, it means that stop profit when it is 1%. Recalculate the assignment. The value of StopProfit is 0.01, that is, 1%
StopLoss /= 100; // Stop loss (backhand), same as above
var eName = exchange.GetName();
if (eName == "Futures_CTP") { // Detect whether the first exchange object added is a commodity futures. If so, throw an error message to stop the robot
throw "Only digital currency futures are supported for now"
}
exchange.SetContractType(Symbol); // Set the digital currency contract code, i.e. the contract to be traded and operated
exchange.SetMarginLevel(MarginLevel); // Set leverage
Interval *= 1000; // The polling interval parameter is converted from seconds to milliseconds
SetErrorFilter("502:|503:|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF"); // Set the type of error to be blocked
StripOrders(exchange); // Cancel all the pending orders
OrgAccount = GetAccount(exchange, true); // Get the current account information
LogStatus("start successfully"); // Update status bar information
var pos = null; // Initialize the local variable pos to null in the main function, which is used to record the position data structure
var positions = GetPosition(exchange); // Get the current positions. Call the encapsulated GetPosition without the orderType parameter to get all positions. Note that the API interface exchange.GetPosition is not called
if (positions.length == 1) { // If there is a position at the beginning, assign a value to the pos variable
pos = positions[0];
Log("A position was found, and the progress has been restored automatically");
} else if (positions.length > 1) { // When there are multiple positions, the strategy cannot be operated and the strategy throws an error to stop the robot
throw "More than 1 position was found";
}
while (true) { // Strategy main loop
pos = loop(pos); // The main function loop of the trading logic is executed, and pos is used as a parameter, and the new position data structure is returned
if (!pos) { // This condition is triggered and null is returned. For example, if the profit is stopped successfully, the number of failures is exceeded, and the asset is insufficient
_Failed = 0; // Reset the time of stop loss to 0 and start again
} else {
_Failed++; // Accumulated time of stop loss
}
Sleep(Interval);
}
}
Strategy logic
After reading the strategy code, we can find that the strategy logic is not complicated and there are not many codes, but the design is ingenious and it can be used for reference in many places.
The main function of the strategy trading logic is the loop
function, which is called repeatedly in the main loop of the main
function. When the loop
function starts executing, it places an order position first, then place an oder to stop profit and waits for the stop-profit order to be filled. After that, it enters the detection state and detects the two items.
Detect whether the pending stop-profit order is fullfilled. If so, that is, profit, exit the detection loop, reset the logic, and start again.
Detect whether to trigger the stop loss (backhand), trigger the stop loss, that is, cancel all the pending orders, close the position, and then set whether to backhand forward position or backhand reverse position to double the backhand order according to the parameter. Generate position, continue to hang out the stop-profit order, and enter the detection state again (monitor the stop-profit and backhand).
The strategy logic is simply described, but there are still some other details, such as the setting of the maximum backhand times, the detection of the available account assets, and the processing of the maximum number of order failures of 10 times.
Some functions in the strategy are designed to behave differently according to different parameters, such as StripOrders
function, GetAccount
function, GetPosition
function. These functions have different behaviors according to the difference of parameter passed in. In this way, the code is well reused, code redundancy is avoided, and the strategy design is concise and easy to understand.
Original strategy: https://www.fmz.com/strategy/3648
Backhand doubling has certain risks, especially in the futures, the strategy is only for learning purpose, caution in the real bot, please feel free to leave your comments and we can have a discussion.
Subscribe to my newsletter
Read articles from FMZ Quant directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
FMZ Quant
FMZ Quant
Quantitative Trading For Everyone