SMA Crossover

This article contains multiple example strategies which trade according to cross of two indicator lines. The first two examples work with any feed but the tick feed - one simply chooses the feed type and parameters in strategy parameter dialog on strategy start. The third example works only with time period aggregation feed.

Any feed

Consider a strategy which trades according to SMA cross direction. On fast SMA going below slow SMA it buys, on the opposite cross - sells.

package jforex.strategies.sdk;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.feed.IFeedListener;
import com.dukascopy.api.feed.util.TimePeriodAggregationFeedDescriptor;

/**
 * The strategy trades according to SMA trend.
 * On up-trend it buys and on down-trend - sells.
 */
public class SmaTrendStrategy implements IStrategy, IFeedListener {

    @Configurable("Feed")
    public IFeedDescriptor feedDescriptor =
            new TimePeriodAggregationFeedDescriptor(
                        Instrument.EURUSD, 
                    Period.TEN_SECS, 
                    OfferSide.ASK, 
                    Filter.NO_FILTER
            );
    @Configurable("Amount")
    public double amount = 0.001;
    @Configurable("Stop loss")
    public int slPips = 10;
    @Configurable("Take profit")
    public int tpPips = 10;
    @Configurable(value="",
          description="close the existing order on creation of a new order if it has not been closed yet")
    public boolean closePreviousOrder = true;
    @Configurable("")
        public int smaTimePeriod = 2;

    private static int LAST = 2;
    private static int PREV = 1;
    private static int SCND_TO_LAST = 0;

    private IEngine engine;
    private IHistory history;
    private IConsole console;
    private IOrder order;
    private IIndicators indicators;

    @Override
    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        // subscribe the instrument that we are going to work with
        context.setSubscribedInstruments(java.util.Collections.singleton(feedDescriptor.getInstrument()), true);
        if(feedDescriptor.getDataType() == DataType.TICKS){
                console.getWarn().println("The strategy can't trade according to the tick feed!");
                context.stop();
        }
        context.subscribeToFeed(feedDescriptor, this);

        IBar prevFeedData = (IBar) history.getFeedData(feedDescriptor, 1);
        submitOrder(prevFeedData.getClose() > prevFeedData.getOpen());
    }

        @Override
        public void onFeedData(IFeedDescriptor feedDescriptor, ITimedData feedData) {

                try {
                        //we need 3 last indicator values to detect the trend change
                        double[] sma = indicators.sma(
                            feedDescriptor, AppliedPrice.CLOSE, feedDescriptor.getOfferSide(), smaTimePeriod)
                            .calculate(3, feedData.getTime(), 0);
                        if (sma[PREV] > sma[SCND_TO_LAST] && sma[LAST] < sma[PREV]) { // down trend
                                submitOrder(!order.isLong());
                        } else if (sma[PREV] < sma[SCND_TO_LAST] && sma[LAST] > sma[PREV]) { // up trend
                                submitOrder(order.isLong());
                        }
                } catch (Exception e) {
                        console.getErr().println(e);
                        e.printStackTrace();
                }
        }

        @Override
    public void onMessage(IMessage message) throws JFException {}

    private void submitOrder(boolean isLong) throws JFException {
        double slPrice, tpPrice;
        Instrument instrument = feedDescriptor.getInstrument();
        ITick lastTick = history.getLastTick(instrument);
        OrderCommand orderCmd = isLong ? OrderCommand.BUY : OrderCommand.SELL;
        // Calculating stop loss and take profit prices
        if (isLong) {
            slPrice = lastTick.getAsk() - slPips * instrument.getPipValue();
            tpPrice = lastTick.getAsk() + tpPips * instrument.getPipValue();
        } else {
            slPrice = lastTick.getBid() + slPips * instrument.getPipValue();
            tpPrice = lastTick.getBid() - tpPips * instrument.getPipValue();
        }
        if(closePreviousOrder && order != null && order.getState() == IOrder.State.FILLED){
                //we don't use order.waitForUpdate, 
                //since our next actions don't depend on the previous order anymore
                order.close();
        }
        order = engine.submitOrder("trend_"+orderCmd.toString() + System.currentTimeMillis(),
            instrument, orderCmd, amount, 0, 20, slPrice, tpPrice);
    }

    @Override
    public void onStop() throws JFException {
        if(order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.OPENED){
            order.close();
        }
    }

        @Override
        public void onTick(Instrument instrument, ITick tick) throws JFException {
        }

        @Override
        public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        }

        @Override
        public void onAccount(IAccount account) throws JFException {
        }

}

SmaCrossStrategy.java

Visual version

Consider adding a couple of visual features to the previous strategy. That is, a chart of the chosen feed gets opened and on that chart the correct indicators get plotted. Moreover, an OHLC informer object gets added to the chart and we configure it such that it shows the indicator values. And finally we add a signal chart object on every cross of the indicator lines.

package jforex.strategies.sdk;

import java.awt.Color;
import java.awt.Font;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.drawings.IChartDependentChartObject;
import com.dukascopy.api.drawings.IChartObjectFactory;
import com.dukascopy.api.drawings.IOhlcChartObject;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.feed.IFeedListener;
import com.dukascopy.api.feed.util.TimePeriodAggregationFeedDescriptor;
import com.dukascopy.api.indicators.OutputParameterInfo.DrawingStyle;

/**
 * The strategy trades according to SMA cross direction.
 * On fast SMA going below slow SMA it buys, on the opposite cross - sells.
 * 
 * The strategy itself opens a chart (if no such chart has been opened yet)
 * and adds the indicator values to the OHLC index.
 */
public class SmaCrossStrategyVisual implements IStrategy, IFeedListener {

    @Configurable("Feed")
    public IFeedDescriptor feedDescriptor =
            new TimePeriodAggregationFeedDescriptor(
                    Instrument.EURUSD, 
                    Period.TEN_SECS, 
                    OfferSide.ASK, 
                    Filter.NO_FILTER
            );
    @Configurable("Amount")
    public double amount = 0.001;
    @Configurable("Stop loss")
    public int slPips = 10;
    @Configurable("Take profit")
    public int tpPips = 10;
    @Configurable(value="", 
    description="close the existing order on creation of a new order if it has not been closed yet")
    public boolean closePreviousOrder = true;
    @Configurable("")
    public int smaTimePeriodFast = 2;
    @Configurable("")
    public int smaTimePeriodSlow = 5;
    @Configurable("")
    public Color fastColor = Color.GREEN;
    @Configurable("")
    public Color slowColor = Color.RED;

    private static int LAST = 1;
    private static int PREV = 0;

    private IEngine engine;
    private IHistory history;
    private IConsole console;
    private IContext context;
    private IOrder order;
    private IIndicators indicators;
    private IChart chart;

    @Override
    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        this.context = context;
        // subscribe the instrument that we are going to work with
        context.setSubscribedInstruments(java.util.Collections.singleton(feedDescriptor.getInstrument()), true);
        if(feedDescriptor.getDataType() == DataType.TICKS){
            console.getWarn().println("The strategy can't trade according to the tick feed!");
            context.stop();
        }
        context.subscribeToFeed(feedDescriptor, this);
        setupChart();        

        IBar prevFeedData = (IBar) history.getFeedData(feedDescriptor, 1);
        submitOrder(prevFeedData.getClose() > prevFeedData.getOpen() );
    }

    private void setupChart(){
        for (IChart c : context.getCharts()){
            if(c.getFeedDescriptor().equals(feedDescriptor)){
                chart = c;
                break;
            }
        }
        //no such chart opened yet - we open it now
        if(chart == null){
            chart = context.openChart(feedDescriptor);
        }
        chart.add(indicators.getIndicator("SMA"), 
            new Object[] { smaTimePeriodFast },
            new Color[] { fastColor },
            new DrawingStyle[] { DrawingStyle.LINE },
            new int[] { 2 });
        chart.add(indicators.getIndicator("SMA"),
            new Object[] { smaTimePeriodSlow }, 
            new Color[] { slowColor },
            new DrawingStyle[] { DrawingStyle.LINE },
            new int[] { 2 });

        IOhlcChartObject ohlc = null;
        for (IChartObject obj : chart.getAll()) {
            if (obj instanceof IOhlcChartObject) {
                ohlc = (IOhlcChartObject) obj;
            }
        }
        if (ohlc == null) {
            ohlc = chart.getChartObjectFactory().createOhlcInformer();
            chart.add(ohlc);
        }
        ohlc.setShowIndicatorInfo(true);
    }

    private void addSignal(boolean isLong, IBar previousBar){
        IChartObjectFactory factory = chart.getChartObjectFactory();
        Instrument instrument = feedDescriptor.getInstrument();
        IChartDependentChartObject signal = isLong ? 
                factory.createSignalUp("signalUp_" + System.currentTimeMillis(), 
                    previousBar.getTime(), previousBar.getLow() - instrument.getPipValue())
                : factory.createSignalDown("signalDownKey_" + System.currentTimeMillis(),
                    previousBar.getTime(), previousBar.getHigh() + instrument.getPipValue());
        signal.setText("SMA cross", new Font("Monospaced", Font.BOLD, 12));
        signal.setColor(isLong ? fastColor : slowColor);
        signal.setStickToCandleTimeEnabled(false);
        chart.add(signal);  
    }

    @Override
    public void onFeedData(IFeedDescriptor feedDescriptor, ITimedData feedData) {

        try {
            long time = feedData.getTime();
            double[] smaFast = indicators.sma(feedDescriptor, AppliedPrice.CLOSE,
                feedDescriptor.getOfferSide(), smaTimePeriodFast)
            .calculate(3, time, 0);
            double[] smaSlow = indicators.sma(feedDescriptor, AppliedPrice.CLOSE,
                feedDescriptor.getOfferSide(), smaTimePeriodSlow)
            .calculate(3, time, 0);
            if (smaFast[LAST] < smaFast[PREV] 
                && smaFast[LAST] < smaSlow[LAST] 
                && smaFast[PREV] >= smaSlow[PREV]
            ) { // smaFast falls below smaSlow
                submitOrder(!order.isLong());
                addSignal(!order.isLong(), (IBar) feedData);
            } else if (smaFast[LAST] > smaFast[PREV] 
                    && smaFast[LAST] > smaSlow[LAST] 
                    && smaFast[PREV] <= smaSlow[PREV]
            ) { // smaFast overtakes smaSlow
                submitOrder(order.isLong());
                addSignal(order.isLong(), (IBar) feedData);
            }
        } catch (Exception e) {
            console.getErr().println(e);
            e.printStackTrace();
        }
    }

    @Override
    public void onMessage(IMessage message) throws JFException {}

    private void submitOrder(boolean isLong) throws JFException {
        double slPrice, tpPrice;
        Instrument instrument = feedDescriptor.getInstrument();
        ITick lastTick = history.getLastTick(instrument);
        OrderCommand orderCmd = isLong ? OrderCommand.BUY : OrderCommand.SELL;
        // Calculating stop loss and take profit prices
        if (isLong) {
            slPrice = lastTick.getAsk() - slPips * instrument.getPipValue();
            tpPrice = lastTick.getAsk() + tpPips * instrument.getPipValue();
        } else {
            slPrice = lastTick.getBid() + slPips * instrument.getPipValue();
            tpPrice = lastTick.getBid() - tpPips * instrument.getPipValue();
        }
        if(closePreviousOrder && order != null && order.getState() == IOrder.State.FILLED){
            //we don't use order.waitForUpdate,
            //since our next actions don't depend on the previous order anymore
            order.close();
        }
        order = engine.submitOrder("cross_"+orderCmd.toString() + System.currentTimeMillis(),
            instrument, orderCmd, amount, 0, 20, slPrice, tpPrice);
    }

    @Override
    public void onStop() throws JFException {
        if(order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.OPENED){
            order.close();
        }
    }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
    }

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }

    @Override
    public void onAccount(IAccount account) throws JFException {
    }

}

SmaCrossStrategyVisual.java

Time period aggregation feed

The SMAStrategy strategy based on two SMA (Simple moving average) : sma with time period 10 and sma with time period 90. The strategy logic can be described as 2 actions:

  • If Sma 10 cross Sma 90 from up to down, then the strategy closes existing short position and open a buy order if long position does not exist
  • If Sma 10 cross Sma 90 from down to up, then the strategy closes existing long position and open a sell order if short position does not exist

The picture below shows two SMA crossover blue line: SMA 10; and yellow line: Sma 90: Sma_crossover

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;

public class SMAStrategy implements IStrategy {
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private int counter =0;

The filteredSma90 and filteredSma10 arrays first element (index 0 ) contains a next-to-last sma value and second element (index 1 ) contains a last candle sma value.

    private double [] filteredSma90; // slow SMA
    private double [] filteredSma10; // fast SMA
    private IOrder order = null;

The strategy contains 3 configurable parameters: trade instrument, indicator period and indicator filters.


    @Configurable("Instrument")
    public Instrument selectedInstrument = Instrument.EURUSD;
    @Configurable("Period")
    public Period selectedPeriod = Period.THIRTY_MINS;
    @Configurable("SMA filter")
    public Filter indicatorFilter = Filter.NO_FILTER;

    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
    }

Method onStop close all created orders.

    public void onStop() throws JFException {
        for (IOrder order : engine.getOrders()) {
            engine.getOrder(order.getLabel()).close();
        }
    }

Method onTick contains all trade logic.

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (!instrument.equals(selectedInstrument)) {
            return;
        }
        IBar prevBar = history.getBar(instrument, selectedPeriod, OfferSide.BID, 1);
        filteredSma90 = indicators.sma(instrument, selectedPeriod, OfferSide.BID, AppliedPrice.CLOSE, 90,
                indicatorFilter, 2, prevBar.getTime(), 0);
        filteredSma10 = indicators.sma(instrument, selectedPeriod, OfferSide.BID, AppliedPrice.CLOSE, 10,
                indicatorFilter, 2, prevBar.getTime(), 0);

SMA10 crossover SMA90 from UP to DOWN , the strategy close existing long position and create a sell order if sell order not created before.

    if ((filteredSma10[1] < filteredSma10[0]) && (filteredSma10[1] < filteredSma90[1])
                && (filteredSma10[0] >= filteredSma90[0])) {
            if (engine.getOrders().size() > 0) {
                for (IOrder orderInMarket : engine.getOrders()) {
                    if (orderInMarket.isLong()) {
                        print("Closing Long position");
                        orderInMarket.close();
                    }
                }
            }
            if ((order == null) || (order.isLong() && order.getState().equals(IOrder.State.CLOSED)) ) {
                order = engine.submitOrder(getLabel(instrument), instrument, OrderCommand.SELL, 0.01);
            }
        }

SMA10 crossover SMA90 from DOWN to UP, the strategy close existing short position and create a buy order if buy order not created before.

        if ((filteredSma10[1] > filteredSma10[0]) && (filteredSma10[1] > filteredSma90[1])
                && (filteredSma10[0] <= filteredSma90[0])) {
            if (engine.getOrders().size() > 0) {
                for (IOrder orderInMarket : engine.getOrders()) {
                    if (!orderInMarket.isLong()) {
                        print("Closing Short position");
                        orderInMarket.close();
                    }
                }
            }
            if ((order == null) || ( not order.isLong() && order.getState().equals(IOrder.State.CLOSED)) ) {
                order = engine.submitOrder(getLabel(instrument), instrument, OrderCommand.BUY, 0.01);
            }
        }
    }    

    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        }

    protected String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }

    public void print(String message) {
        console.getOut().println(message);
    }

}

SMAStrategy.java

The information on this web site is provided only as general information, which may be incomplete or outdated. Click here for full disclaimer.