How To Use Quant Zone To Automate Your Trading
Please be very careful when building Quant Zone rules. A small mistake can lead to a large loss of funds as the QZ will continue to run every 15 seconds as long as the conditions set by the user are met, so be sure to double and triple check both the condition and the actions to ensure the QZ is coded to do exactly what you want. It is advised that after saving your QZ rule that you monitor its actions for a bit to make sure it is acting as intended.
None of the following is trading advice, example trades are merely for illustrative purposes. Users should use the Quant Zone at their own risk.
What is Quant Zone? The basics:
QZ makes it very easy to build automated trading strategies allowing users to execute the most basic of strategies like scaling into/out of a position, to more complex strategies that would be hard to accomplish manually, such as basis trades, TWAPs, etc. Users can take advantage of this to decrease the amount of time and work needed to trade, to reduce slippage, and take advantage of opportunities 24/7/365.
A QZ rule is made up of two parts, triggers and actions. The trigger is a condition, or set of conditions, that when met, trigger the QZ to execute the desired actions. When triggered, the QZ runs every ~15 seconds.
The conditions can be a wide range of functions, like your position size on a given market or across your account as a whole; your account balance, available balance, or leverage; or the price of bitcoin, the spread between spot and futures, and much more. You can see a comprehensive list of possible functions here.
After you create your condition, you will either see “Current value: true”, in which case that means the QZ will trigger under current conditions, or “Current value: false” which would mean under current conditions the QZ would not be triggered.
The action is what the QZ does when the condition is met, and usually involves placing orders, opening or closing positions, or pausing/unpausing your QZ rule.
Building a Futures Position:
Choosing your trigger:
First you need to decide what condition will trigger the QZ into action. For example, to build a position in a certain market you would need to choose the position function, the market you want to build your position in, and what size position you want.
Let’s say you want to build a 10btc long position on BTC-1231, you would create the following condition for your trigger:
position(“BTC-1231”) < 10
This means that any time your position size is less than 10btc, QZ will continue to execute the action. Once the position reaches 10btc, it would no longer execute.
You can also define the position size for your trigger in terms of notional (USD) value. For example, if you wanted to open a $500,000 position, you would use the following condition:
To build a position in a different market, just replace BTC-1231 with the desired market you would like to trade, e.g., ETH-0625, SRM-PERP, etc. The position variable always uses the quote currency (i.e., BTC, ETH, SRM) and position_notional always uses the USD value.
Building an action with market orders:
Now you need to decide what action you want QZ to execute when the above condition is triggered. As BTC-1231 isn’t quite as liquid as perps, you may want to execute smaller trades to build your position, so let’s set the action to the following:
The above action means that as long as the condition you set above (position(“BTC-1231”)<10) is true, every 15 seconds QZ will submit a market buy order for 0.01btc on BTC-1231. At that rate, QZ would buy 2.4btc per hour, so it would take just over 4 hours to build your position to the desired size of 10btc. If you’d like to spread it out over a longer period, just reduce the order size (at 0.001btc it would take just under two days to build your desired position).
When setting your quantity, you may want to check the contract specs for the market you’re trading to make sure the order size isn’t below the quantity step, as that would prevent any orders from being placed.
Building an action with limit orders:
Another option would be to use limit orders instead of market orders, allowing you to define your price, avoid any slippage, and the option of paying the lower maker fees as opposed to taker fees. The flipside, however, is that with limit orders your orders aren’t instantly filled as they are with market orders, so it may take longer to build your position.
With a limit order, we have to determine more variables, namely what price we want to use for the order, and whether or not to keep the existing order open or cancel it and place a new order if one of your previous orders didn’t fill.
We have a couple of options we can use to set the price of the limit order, namely “offer_price(“BTC-1231”)” and “bid_price(“BTC-1231”)” which would set the price to the best offer price or best bid price, respectively, at the time the action is triggered. This ensures that your price stays inline with the market as it moves.
You can then add variables from there so that your price would end up just above the best bid, or just below the best offer. For example, the following would submit an order at $1 below the current best offer:
offer_price(“BTC-1231”) — 1
And the following would submit an order at $1 above the current best bid:
bid_price(“BTC-1231”) + 1
Of course you can decide what amount to change the order by, $1, $5, $10, etc, it’s up to you. Just make sure you’re abiding by the tick size of the market you’re trading as defined here, otherwise the system will round down to the nearest appropriate price (58000.5 would be rounded down to 58000).
You can also set your offset in terms of percent. For example, if you wanted to submit an order 10bps (0.1%) above best bid, you could use the following:
bid_price(“BTC-1231”) * 1.001
For 10bps below best offer, it would be:
offer_price(“BTC-1231”) * 0.999
Now we need to decide what to do when our orders don’t fill; we have two options, keep the existing order open, or cancel it and place a new order. If we elect to keep the existing order open, the QZ won’t place any additional orders and will stop running until the existing order is filled. If we choose to cancel it and place a new order, the existing order will be canceled and a new order will be placed. The QZ runs every 15 seconds, so if you elect to cancel the existing order, your limit orders would have 15 seconds to fill before being canceled and replaced. As such, you may want to keep the price somewhat close to best offer when trying to buy and best bid when trying to sell to increase the chances of your orders executing.
A setting you may want to enable when using limit orders is the post-only toggle, this ensures that your order doesn’t execute against any resting orders thereby guaranteeing you pay a maker fee and not a taker fee. If it was to execute against an existing order, your order would be canceled and placed again in the next QZ cycle (~15 seconds).
Closing A Futures Position:
To close a futures position, we’re basically just going to do the opposite of what we did to build the futures position, with one small variation: I want to change the condition to trigger when my position is above 0 instead of triggering when it is below 10 like in the above example. So in this case, my condition would be the following:
position(“BTC-1231” , “buy” ) > 0
This means that as long as your long position size is above 0, i.e., you have an open position, the QZ will continue to trigger actions. As soon as your long position is closed, the QZ will cease to submit sell orders.
Now for my action, instead of setting a limit buy or market buy like above, I would choose sell instead:
One option you may want to use when closing a position, is to enable the reduce only toggle. This ensures that any orders submitted would only reduce your position, thereby preventing you from submitting an order that might flip you from long to short (e.g, if you have your order size at 0.1, and your existing position size is 0.05, if that sell order executed you would now be short 0.05btc, if the reduce only toggle was enabled, that order would not be submitted)
Note that if you’re trying to close a short position, you would use the following condition, as the “sell” refers to a short position whereas the “buy” in the example above refers to a long position:
position(“BTC-1231” , “sell” ) > 0
Building a spot position:
In addition to building futures positions, QZ can easily be used to build a position through spot markets as well. Your condition here would be based on your account balance, so let’s say you wanted to buy 10btc on BTC/USD, you would use the following condition:
balance(“BTC”) < 10
This would instruct the QZ to trigger anytime your BTC balance was below 10btc.
Similar to how we can choose notional size for futures positions, we can also choose to set our condition to buy until we hold a certain USD value of BTC. For example, if we wanted to hold $100,000 worth of BTC, we can use the following condition:
balance(“BTC”) * price(“BTC/USD”) < 100000
This tells QZ that anytime your BTC holdings equal less than $100,000 to trigger the action.
The actions would be the same as above, except we would buy on the BTC/USD market, for example:
So using the parameters as defined above, anytime your BTC balance is worth less than $100,000 the QZ will place a market buy order for 0.01btc.
Converting profits from USD to BTC:
Something we’ve received a lot of requests for is BTC based pnl, and while this isn’t a perfect solution, an easy way to convert USD profits to BTC is through QZ. To accomplish this, you’ll want to use the same function as before, but for USD. So your condition would look like this:
balance(“USD”) > 50000
The above function would instruct QZ to trigger anytime your USD balance is above $50,000. You may want to set the value above 0 to prevent your USD balance going negative on any small move against your futures positions, thereby requiring you to borrow USD.
For the action, I can have QZ submit limit or market orders similar to the examples above.
Executing a basis trade:
A basis trade is a trade in which you profit off the difference between the spot price and futures/perp price by buying/selling on futures/perps and executing a trade in the opposite direction on spot (e.g., sell futures, buy spot). For perpetual markets, this allows you to collect funding and on dated futures products this allows you to capture the premium/discount. For this example I’ll explain how to do this on the perpetual markets.
First, you want to decide at what premium you want to enter your basis trade. For example, maybe you don’t want to tie up your capital if the premium is only 10bps (~36.5% annualized), but if the premium was 20bps (~73% annualized) then you would want to execute a basis trade.
Then you need to decide how big of a position you want for this basis trade. For this example, we’ll use 1btc.
Your condition would then look like this:
(premium(“BTC-PERP”) > 1.002) and (position(“BTC-PERP”, “sell”) < 1)
This is saying that if the premium of BTC-PERP to the BTC Index is over 0.2% AND the total position size of your BTC-PERP short is less than 1, the QZ should trigger into action. If either the premium dips below 20bps or your position size reaches 1btc the QZ will no longer run.
For this QZ rule, you’ll need two actions to make sure your basis position is hedged, one action for selling BTC-PERP and one action for buying BTC/USD. For this example I’ll be using market orders to ensure that both my futures and spot orders get executed, because with limit orders you might have a situation where one leg of the trade gets executed but the other doesn’t, leaving you unhedged (i.e., if your perp sell order is filled, but your spot buy order isn’t, if the price goes up you’d be losing money on the short without gaining that money back through spot holdings).
Next you’ll need to make another rule to close out your basis trade position in case the premium goes away or even against you. To do this, create another QZ rule.
For the condition, I need to choose at what premium I want to start unwinding my position. For this example I’ll use 2.5bps. So my condition would look like this:
(premium(“BTC-PERP”) < 1.00025) and (position(“BTC-PERP”, “sell”) > 0)
So as long as the premium is less than 0.025% and I still have an open short position on the perps, I will want the QZ to trigger my actions to close out the position. My actions are going to be the same as when I opened it, just in reverse this time (buy perps, sell spot)
One thing to be cognizant of as you’re using market orders here is to ensure that the funding payments you’ll receive is more than the trading fees, as you’re making 4 separate trades on this position (entry on spot, entry on futures, exit on spot, exit on futures), so this isn’t necessarily a trade that under normal circumstances you’d want to move into with a short time horizon (e.g., if funding is paying 20bps an hour and your trading fees are 5bps taker, if you only hold the position for one hour and receive 1hr worth or funding payments, your trading fees and amount gained from collecting fund would cancel out). As such, you might want to have a decent spread between what premium you’d enter into the position and which premium you will exit the position to prevent excessive entry/exit of positions incurring more trading fees.
Another option to prevent excessive trading due to small variances in the perp premium/discount to index is adding another action to my close basis trade rule that would pause the open basis trade rule for a set period of time. Such as this:
With the above rule, anytime my close basis trade condition is met, it would pause the open basis trade rule for 1 day. That prevents a scenario where in volatile markets, the premium might dip triggering my QZ rule to start closing my basis position, then shortly thereafter increase again triggering my QZ rule to start opening/increasing my basis position, then dips again causing it to start closing, etc… This would result in excessive trades being made, so by pausing the open basis rule whenever the close basis rule is triggered, you would prevent that scenario from occurring.
You should double check before doing this trade is the quantity step for the markets that you’re trading, they may vary between the futures and spot market.
Lastly, you should note that QZ function “premium(“BTC-PERP”)” is the current premium of BTC-PERP to BTC Index, it is not equivalent to and is much more volatile than the expected funding rate which is based on a TWAP (i.e., average) of the premium over the last hour. So momentary dips in the premium could trigger your QZ function to close your basis trade, even if the expected funding rate was above your defined threshold.
As mentioned at the top of this article, you need to be extremely cautious when using the Quant Zone as it will execute any action you construct as long as the condition you set is met, regardless of whether or not the action is what you intended to build, or the condition is what you intended it to be. In light of this, you may want to make use of a couple safeguards and regularly check your account when QZ rules are enabled.
To build these, we need to create a new rule and determine some boundaries that your QZ rules were intended to stay within. For example, in my basis trade above, I set the condition to stop triggering if my btc position exceeded 1btc, so if due to some error on my part when building the rule I ended up with a 2btc short, I know something is wrong with the rule and I want to pause the rule before any more damage is done. But remember, I’m also buying spot with my basis trade, so I might want to add a protection for that condition as well, so I could set the condition as:
(position(“BTC-PERP”, “sell”)) > 2 or balance(“BTC”) > 2
And for action I would choose the following:
This means that if either my BTC-PERP short position ever exceeded 2btc, or my BTC balance ever exceeded 2btc, I want QZ to pause my rule for opening the basis position for 1 day preventing any more unintended trades to be executed.
You can make safeguards for all kinds of scenarios, such as if you’re building a position but meanwhile the market moves against you increasing your leverage beyond desired levels, you could create a rule with the following condition:
Leverage > 10
Set your action to “pause rule”, choose a specific rule you would like it to pause and QZ will stop executing that rule if your leverage ever exceeds 10x. You can also elect to pause all rules if your condition is met.
Or say you never want to have more than $100,000 worth of positions, you could use the following condition:
total_position_size > 100000
There are many other conditions you can use (margin fraction, approximate distance from liquidation, or simply total account collateral) to trigger a safeguard. It’s wise to consider putting some in place while testing out the Quant Zone and familiarizing yourself with how it works to avoid rules executing beyond what you intended. You can see a complete list of possible functions here.
If you like this article and are interested in more articles on how to use the QZ to deploy trading strategies, please let us know
You can read more about Quant Zone in the FAQ on our Help Desk.
Examples for more complicated QZ rules (TWAP, EMA, SMA crossover, range trading strategies) can be found here
If you have any questions on the above, don’t hesitate to reach out and ask questions.