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
var 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.