TWAPStrategy

Time-Weighted Average Price (TWAP) is a trading algorithm based on weighted average price used to execution of bigger orders without excessive impact on the market price.

Strategy settings

TWAP Strategy parameters
Target quantity Overall quantity to be realized by strategy
Step Size Quantity to realize in single order
Delay(ms) Delay time between following orders in ms
Randomize delay by 50% Randomizing delay by 50%
Randomize order size by 50% Randomizing order size by 50%
Start Time Time when strategy begins to submit orders
End Time Time when strategy ends to submit orders
Price Limit Price limit for orders
Max Volume Particip(%) Maximum market’s volume participation
Side Market side for orders

Case scenario

TWAP Strategy is set to buy 10,000 shares in packages of 10 shares each with 1 minute delay between them, additionally strategy is allowed to randomize order size and delay by 50%. Price limit is set to $147 and on top of that, strategy will work from 10:00 AM to 4:00 PM.

TWAP Example view

After strategy is done there can be seen that settings affected execution. In first place strategy started to buy shares after 10:00 AM as was set, also it did not buy shares more expensive than $147. In Strategy orders widget orders quantity is in range of 10 randomized by 50% and sending time of orders is equivalent to delay.

TWAP Strategy orders

In Profit & Loss widget there can be seen that our strategy did not realized whole target quantity, but only part of it. It happened to not violate TWAP Strategy settings.

TWAP Strategy profit & loss

Development

TWAP Strategy is composed of two trading actions.

  • NEW_ORDER action

Action is responsible for making new orders.

action(tradingAction
        .frequency(customFrequency)
        .actionType(TradingActionType.NEW_ORDER)
        .condition(CHECK_START_TIME.and(QUANTITY_LEFT.and(VOLUME_PARTICIPATION_OK).and(PRICE_OK)))
        .createOrderWith(
                        orderBuilder
                                        .withSymbol(prop_marketSymbol)
                                        .side((x) -> prop_side)
                                        .withPriceLimit(BEST_QUOTE)
                                        .withQuantity(RANDOMIZED_QUANTITY))
        .onNewOrderSent((order) -> {
                addToCounter(ON_MARKET, order.getMarketSymbol(), order.getOrderQuantity());
                activeOrdersID.add(order.getClientOrderID());
        })
        .onOrderFilled(UPDATE_REALIZED)
        .onOrderRejected((report) -> subtractFromCounter(ON_MARKET, report.getMarketSymbol(), report.getOrderQuantity()))
        .onOrderCancelled((report) -> subtractFromCounter(ON_MARKET, report.getMarketSymbol(), prop_stepSize.subtract(report.getCummulativeQuantity()))))

Action is executed regularly with delay given by the user. Every time before execution strategy checks conditions - if it is proper time to start action, if there is enough quantity left to make order, if strategy doesn’t have too much market volume participation and if the price is in the limit. After all conditions are passed then strategy makes order, sends it to the market and then reacts properly to received execution report.

  • CANCEL_ORDER action

Action is responsible for canceling old orders.

action(cleanUpAction
        .frequency(10000)
        .actionType(TradingActionType.CANCEL_ORDER)
        .condition(() -> !activeOrdersID.isEmpty())
        .createOrderWith(
                        orderBuilder
                                        .withSymbol(prop_marketSymbol)
                                        .originalOrderID(() -> activeOrdersID.remove()))
        .onOrderFilled((report) -> {
                activeOrdersID.remove(report.getClientOrderID());
        }))

Action is executed regularly every 10 seconds and it cancels oldest not filled order. It is made to maintain good flow of strategy and prevents holding outdated orders.

In strategy instance there is also condition to check if strategy is done.

protected Condition DONE = () -> isDone();

private boolean isDone() {
        if (getCounter(TARGET, prop_marketSymbol).compareTo(getCounter(REALIZED, prop_marketSymbol)) <= 0)
                return true;

        if (new Date(getLastEventTime()).after(prop_endTime)) {
                getPlatform().sendInfo("Strategy time passed");
                return true;
        }
        return false;
}

Strategy is finished when there is no more target quantity to execute or the end time set in settings has passed.