Mariner Backtesting - order.twap()

Time Weighted Average Pricing (TWAP) Orders

CQ Elite Only

The Time-Weighted Average Pricing (TWAP) order achieves time-weighted average price by time slicing the order over a period of time specified by the order creator. This strategy is used to accumulate (or liquidate) a position over the specified time. The orders need to be sliced in such a way as to help hide the accumulation of the position from others watching the market activity.

TWAP orders can be for buying or selling (including short selling)

The most common use of TWAP is for distributing big orders throughout the trading day. For example, you wish to accumulate 20,000 shares of CURE, an ETF which typically trades only 170,000 shares per day. Putting one big order would vastly impact the market and the price most likely would start to raise. To prevent that, you define a time period in TWAP Strategy over which they want to buy shares. It will slice the big order into smaller ones and execute them over the defined time frame.

Not for use with live trading

This order.twap() method is for use with backtesting only. It isn't supported in live trading.

Parameters
Name Type Required? Default Information
symbol string Yes   The trading Symbol
Order Quantity integer Yes   Total number of shares to buy or sell. Always positive number
side string Yes   "buy" or "sell"
start_time muts No (immediately) service.system_time In UTC, the time to start making TWAP orders
end_time muts No md.market_close_time - 30 sec In UTC, the time to end making TWAP orders
time_frame integer No None The number of minutes for the TWAP to run starting at the start time. If specified, this will override the specified end time.
order_aggression integer No 1 Determines the aggressiveness of the sub-orders submitted to the market.
1 = Very Aggressive (market suborders) - Default behavior
2 = Aggressive (marketable limit suborders for likely immediate fill)
3 = Midpoint order
4 = Passive (sub orders join the best bid or offer)
num_slices integer No order_quantity / 100 The number of suborders to use in submitting the order to the market.

Please Note: All other optional parameters for _algo_order can be passed in (user_key, exit_script, etc.)

SubOrder pricing

The suborder pricing always uses the best bid best offer at the time of the order slice.

SubOrders always fill

The suborders will always fill. If a suborder reaches the end of the timeframe for the given slice, the TWAP will cancel the open suborder and add in a market order so that the order is immediately filled.

Buy or Sell

The TWAP reviews the position as it submits suborders. Therefore it is not necessary to specify Sell Short if you are going short. If the account has a zero position or is expanding its position then the TWAP will correctly identify the suborders prior to submission.

Returns
Type Special Notes
integer order_id unique twap/vwap order identifier

The return integer is a "handle" to the overall TWAP order. This handle can be used to cancel the order.

Working Examples: Simple TWAP to Buy 5000 shares

Buying 5000 Shares time sliced over the next 10 minutes, starting immediately. All suborders will start as limit orders with Midpoint Pricing.

 order.twap(self.symbol, 5000, "buy", time_frame=10,order_aggression=3)
Simple TWAP to sell 7500 shares starting at 5 min before close

This TWAP will start 5 minutes before the close and will end 30 seconds before the end of the trading day. All suborders will be entered as market orders.

 twap_start = md.market_close_time - service.time_interval(0,5) # start_time = 5 min before close
c_twap = order.twap(self.symbol, 7500, "sell", start_time=twap_start)
Cancelling a TWAP order
 c_twap = order.twap(self.symbol, 7500, "sell", start_time=twap_start,
                         time_frame=10, order_aggression=3)
order.twap_cancel(c_twap)
Full working example:(Link)
 # Copyright Cloudquant, LLC. All right reserved.
from cloudquant.interfaces import Strategy

class TwapExample(Strategy):

    @classmethod
    def is_symbol_qualified(cls, symbol, md, service, account):
        return symbol == 'GOOG'

    def on_start(self, md, order, service, account):
        twap_start = md.market_open_time + service.time_interval(0,1) # start_time = 1 min after open
        twap_end = md.market_open_time + service.time_interval(0,6) # end_time = 6 min after open

        order.twap(self.symbol, 500, "buy", start_time=twap_start, end_time=twap_end, order_aggression=4)

    def on_minute_bar(self, event, md, order, service, account, bar):
        if event.timestamp > md.market_open_time + service.time_interval(0,1):
            print(service.time_to_string(event.timestamp) + "\tin on_minute_bar()")

            # print position information
            print(self.symbol + ' Information:\n')
            print(account[self.symbol].position)

            if event.timestamp > md.market_open_time + service.time_interval(0,6):
                service.terminate()

Console Output from this script

 2017-12-05 09:31:02.582000  in on_minute_bar()
GOOG Information:

        shares          :              0
        entry_price     :           0.00
        mtm_price       :           0.00
        capital_long    :           0.00
        capital_short   :           0.00
2017-12-05 09:32:00.272000  in on_minute_bar()
GOOG Information:

        shares          :            100
        entry_price     :         991.00
        mtm_price       :         991.00
        capital_long    :       99100.00
        capital_short   :           0.00
2017-12-05 09:33:00.195000  in on_minute_bar()
GOOG Information:

        shares          :            200
        entry_price     :         993.50
        mtm_price       :         993.50
        capital_long    :      198700.00
        capital_short   :           0.00
2017-12-05 09:34:01.833000  in on_minute_bar()
GOOG Information:

        shares          :            300
        entry_price     :         996.33
        mtm_price       :         996.33
        capital_long    :      298900.00
        capital_short   :           0.00
2017-12-05 09:35:00.452000  in on_minute_bar()
GOOG Information:

        shares          :            400
        entry_price     :         997.25
        mtm_price       :         997.25
        capital_long    :      398900.00
        capital_short   :           0.00
2017-12-05 09:36:01.567000  in on_minute_bar()
GOOG Information:

        shares          :            500
        entry_price     :         998.20
        mtm_price       :         998.20
        capital_long    :      499100.00
        capital_short   :           0.00