Basic strategy implementation

Let’s get into the Strategy API by building step-by-step very simple, yet fully working strategy.

Strategy will be sending BUY orders whenever current market price of given instrument falls below the moving average of last 100 trades.

First things first

We start with a new strategy class having basic functionality: subscribing to market data feed and logging out incoming trade information.

The entire strategy is defined in the single Java class:

public class BasicEchoStrategyExt extends StrategyExt {

    @Symbol
        private MarketSymbol marketSymbol;
        private MarketDataType[] CONTENT = {    MarketDataType.LATEST_TICK };

        @Override
        protected void initialize(StrategyIntializer strategy) throws PropertyValidationException {

                strategy.subscribeToFeed(marketSymbol, CONTENT)
                        .tradeReceived(TRADE);
        }

        private Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {
                @Override
                public void accept(TradeEvent trade) {
                        getPlatform().sendInfo("got trade: "+ trade);
                }
        };
}

Let’s look closer at the code:

Initialization

The initialize() method is called by the framework after pressing START button in the strategy window. Strategy is initialized using StrategyInitializer interface given as the parameter.

In the code above we use following initializer methods:

  • subscribeToFeed() - creates the subscription to market data for given symbol and events listed in the CONTENT parameter.

(marketSymbol variable contains the symbol selected by the user from the list of symbols in strategy window)

  • tradeReceived() - registers a method which will be called for each received TradeEvent

Please note that each call to StrategyInitializer returns the reference to the same object, so we can chain all calls together.

Event consumers

The consumer of the market data event must define the accept() method for the given event type - for example to handle trade event we define following method:

public void accept(TradeEvent trade) {
        getPlatform().sendInfo("got trade: "+ trade);
}

In the sample strategy code we define an anonymous inner class implementing single functional interface Consumer<>

protected Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {...}

If the accept method is not too complex we can use lambda expression instead of inner class, like this one:

(trade)->getPlatform().sendInfo("got trade: "+ trade)

Lambda expression can be used directly as the parameter of the initializer method, like below:

strategy
        .subscribeToFeed(marketSymbol, CONTENT)
        .tradeReceived( (trade)->getPlatform().sendInfo("got trade: "+ trade));

Note

It is up to developer to choose which style is preferred - by using lambda expressions we keep the code shorter, on the other hand too many of them can be problematic to understand in more complex cases. In following chapters we use both styles.

Calculations and reporting action

Next step is to add calculation of the simple moving average of last 100 trades price and logging the mean price every 10 seconds.

public class BasicEchoStrategyExt extends StrategyExt {

    @Symbol
    private MarketSymbol marketSymbol;
    private MarketDataType[] CONTENT = { MarketDataType.LATEST_TICK };
    private static MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
    private SimpleMovingAverage averageTradePrice = new SimpleMovingAverage(100, mc);

    @Override
    protected void initialize(StrategyIntializer strategy) throws PropertyValidationException {

                ActionBuilder reportingAction = createActionBuilder();

                strategy.subscribeToFeed(marketSymbol, CONTENT)
                         .tradeReceived( TRADE)
                         .action ( reportingAction
                                .frequency(1000)
                                .execute(() -> getPlatform().sendInfo(" average price:" + averageTradePrice.getMeanValue() )));
        }

    private Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {

                @Override
                public void accept(TradeEvent trade) {
                        averageTradePrice.add(trade.getPrice());
                }
    };
}

Action builder

As shown above, we initialized strategy with a new kind of method - so called “action”.

To add an action we need ActionBuilder object:

ActionBuilder reportingAction = createActionBuilder();

We initialize action using action builder:

reportingAction
        .frequency(1000)
        .execute(() -> getPlatform().sendInfo(" average price:" + averageTradePrice.getMeanValue()));

Actions, unlike consumers, are not binded to any particular event type.

Action can be triggered on demand or invoked automatically by the strategy executor. In this example action is repeated automatically every 1000 milliseconds - this is achieved by using action builder frequency() method.

Builder execute() method specifies an instance of Runnable interface which will be executed each time action is invoked. Lambda expression is allowed (see note in previous chapter).

Having the action builder initialized we can use it as parameter to strategy.action() method:

strategy
        .action(reportingAction
                .frequency(10000)
                .execute(() -> getPlatform().sendInfo(" average price:" + averageTradePrice.getMeanValue() )));

To complete this example we have modified TRADE consumer, in accept() method we use utility class SimpleMovingAverage to keep track of last 100 trade prices:

private Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {

        @Override
        public void accept(TradeEvent trade) {
                averageTradePrice.add(trade.getPrice());
        }
};

Note

Strategy developer must not make any assumptions which strategy method (consumer or action) will be executed first. In our example reporting action can be triggered before any trade event was received or after 1000 trades - it depends on the incoming market data flow and action frequency.

Conditional action execution

Usually we want actions to be executed when certain conditions are fulfilled - for example we may want to report only when price is below average. To achieve that we use action builder condition() method:

.action (reportingAction
        .frequency(1000)
        .condition(PRICE_OK)
        .execute(() -> getPlatform().sendInfo(" price:" + averageTradePrice.getNewest())))

private Condition PRICE_OK = new Condition() {

        @Override
        public boolean test() {
                return averageTradePrice.getNewest().compareTo(averageTradePrice.getMeanValue()) < 0;
        }
};

the equivalent using lambda expression would be:

.action(reportingAction
       .frequency(1000)
       .condition(()-> averageTradePrice.getNewest().compareTo(averageTradePrice.getMeanValue())<0)
       .execute(()-> getPlatform().sendInfo(" average price:" + averageTradePrice.getMeanValue())))

Note that in this example condition is checked in regular intervals of 1 second.

Trading action

Finally we add sending market order each time the latest trade price is below the moving average of last 100 trades.

We add second action “tradingAction” for sending orders.

The initialize() method looks like:

protected void initialize(StrategyIntializer strategy) throws PropertyValidationException {

  ActionBuilder reportingAction = createActionBuilder();
  ActionBuilder tradingAction = createTradingActionBuilder();
  OrderBuilder orderBuilder = createOrderBuilder(marketSymbol);

  strategy
        .subscribeToFeed(marketSymbol, CONTENT)
        .tradeReceived(TRADE)
        .action (reportingAction
                .frequency(1000)
                .condition(PRICE_OK)
                .execute(() -> getPlatform().sendInfo(" average price:"+averageTradePrice.getMeanValue())))
        .action (tradingAction
                .actionType(TradingActionType.NEW_ORDER)
                .condition(PRICE_OK)
                .createOrderWith( orderBuilder
                        .withSymbol(marketSymbol)
                        .side( (s)->Side.Buy )
                        .withPriceLimit( (s)-> averageTradePrice.getNewest() )
                        .withQuantity( (s) -> BigDecimal.ONE )));
}

Trading action is constructed with TradingActionBuilder which has standard action attributes like condition() and frequency() plus additional methods: CreateOrderWith() and actionType().

Order details are set up using interface OrderBuilder. This interface has methods to specify how to calculate order side, price limit, quantity, etc.

Note that in the code above we omit the .frequency attribute of the action. This means we don not want this action to be automatically called by the framework in regular intervals. Instead we want to invoke (trigger) this action on demand. To achieve this we can amend trade consumer code:

private Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {

        @Override
        public void accept(TradeEvent trade) {
                averageTradePrice.add(trade.getPrice());
                triggerTradingActions(trade.getMarketSymbol());
        }
};

There is no explicit dependency between trade consumer and trading action. This allows for more flexible and maintainable strategy code.

Summary

How does it all work together

  • When strategy is initialized two actions are set up: reportingAction and tradingAction. Trading action is initialized with the order builder and condition PRICE_OK
  • When strategy runs, on every received trade TRADE consumer is invoked. This consumer is updating average data and triggers trading action. Invoked action checks its condition (PRICE_OK) - if the condition is fulfilled action will create a new order using order builder. Order parameters are set using given expressions for: side, priceLimit, quantity - for each new order the order builder will evaluate these parameters again. In our example price limit will be always equal to last trade price.

Note

Order parameters are evaluated at run-time, not during strategy initialization - that is why strategy must provide order builder with functions (functional interfaces) : PriceSupplier, QuantitySupplier, SideSupplier

Current version of strategy executor is single-threaded, so there is no concurrent execution of actions or consumers, still it is recommended to keep it thread safe and not rely on any assumptions about order in which actions are invoked.

Complete working code

public class BasicStrategyExt extends StrategyExt {

    @Symbol
    private MarketSymbol marketSymbol;
    private MarketDataType[] CONTENT = { MarketDataType.LATEST_TICK };
    private static MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
    private SimpleMovingAverage averageTradePrice = new SimpleMovingAverage(100, mc);

        @Override
        protected void initialize(StrategyIntializer strategy) throws PropertyValidationException {
                ActionBuilder reportingAction = createActionBuilder();
                ActionBuilder tradingAction = createTradingActionBuilder();
                OrderBuilder orderBuilder = createOrderBuilder(marketSymbol);

                strategy
                        .subscribeToFeed(marketSymbol, CONTENT)
                        .tradeReceived(TRADE)
                        .action(reportingAction
                                        .frequency(1000)
                                        .condition(PRICE_OK)
                                        .execute(() -> getPlatform().sendInfo(" average price:" + averageTradePrice.getMeanValue())))
                        .action(tradingAction
                                        .actionType(TradingActionType.NEW_ORDER)
                                        .condition(PRICE_OK)
                                        .createOrderWith(orderBuilder
                                                        .withSymbol(marketSymbol)
                                                        .side((s) -> Side.Buy)
                                                        .withPriceLimit((s) -> averageTradePrice.getNewest())
                                                        .withQuantity((s) -> BigDecimal.ONE)));
        }

        private Condition PRICE_OK = new Condition() {

                @Override
                public boolean test() {
                        return averageTradePrice.getMeanValue() != null
                                        && averageTradePrice.getNewest().compareTo(averageTradePrice.getMeanValue()) < 0;
                }
        };

        private Consumer<TradeEvent> TRADE = new Consumer<TradeEvent>() {

                @Override
                public void accept(TradeEvent trade) {
                        averageTradePrice.add(trade.getPrice());
                        triggerTradingActions(trade.getMarketSymbol());
                }
        };
}

Results

Following picture shows results of strategy execution in TradePad back test mode with some historical market data:

basic example results