Stop Loss

Regarding conditional orders Stop Loss and Take Profit, it is important to reiterate that an IOrder object represents a position of a main order which may have optionally attached Stop Loss and/or Take Profit orders. This means that:

  1. On submitting a main order with nonzero Stop Loss and Take Profit prices, both Stop Loss and Take Profit orders automatically get created.
  2. On meeting Stop Loss or Take Profit condition, the main order gets closed and any other attached conditional orders get cancelled (e.g. Take Profit order gets cancelled on meeting Stop Loss).
  3. On closing the main order all attached conditional orders get cancelled.

There are two ways one can set the Stop Loss price for an order:

  • by setting the Stop Loss price upon order creation:
    engine.submitOrder("order", Instrument.EURUSD, OrderCommand.BUY, 0.001, 0, 20, 1.2222, 0);
  • or by modifying an already created order:
    order.setStopLossPrice(1.2222);

Set Stop Loss to an existing order

In order to set Stop Loss price the following conditions must be met:

  1. Order must be in state FILLED or OPENED.
  2. Stop Loss price must be divisible by 0.1 pips, otherwise the order will be rejected.
  3. One can not set Stop Loss price more than once within a second.

IOrder interface provides 3 methods for creating or modifying Stop Loss price of an existing order. method void setStopLossPrice(double price):

IOrder order = engine.getOrder("order");
order.setStopLossPrice(1.2222);

method void setStopLossPrice(double price, OfferSide side) lets you specify the side used in the Stop Loss condition:

order.setStopLossPrice(1.2222, OfferSide.ASK);

and the last but not least void setStopLossPrice(double price,OfferSide side, double trailingStep) lets you set the trailing step:

order.setStopLossPrice(1.2222, OfferSide.BID, 20);

Note that the value of the trailing step parameter must be above or equal to 10 in order to be successfully applied.

Cancelling Stop Loss

If the specified Stop Loss price equals zero, Stop Loss condition will not be applied. In the following example we remove the Stop Loss condition from an existing order:

IOrder order = engine.getOrder("order");
order.setStopLossPrice(0);

Detect close by Stop Loss

In order to detect that the order was closed by meeting the Stop Loss condition, one has to check in IStrategy.onMessage if the IMessage.getReasons() contain IMessage.Reason.ORDER_CLOSED_BY_SL. For more on messages workflow see the Order state article. Consider the following strategy which causes one order to close on meeting the Stop Loss price and another order to close unconditionally:

@Override
public void onStart(IContext context) throws JFException {
    console = context.getConsole();
    engine = context.getEngine();
    history = context.getHistory();

    IOrder order = engine.submitOrder("orderCloseBySL", instrument, OrderCommand.BUY, 0.001);
    order.waitForUpdate(2000, IOrder.State.FILLED);
    // on purpose put SL such that the order gets immediately closed on the SL
    double slPrice = history.getLastTick(instrument).getAsk() + instrument.getPipValue() * 10;
    order.setStopLossPrice(slPrice);

    // market BUY for unconditional close
    order = engine.submitOrder("orderUnconditionalClose", instrument, OrderCommand.BUY, 0.001);
    order.waitForUpdate(2000, IOrder.State.FILLED);
    order.close();
}

@Override
public void onMessage(IMessage message) throws JFException {
    IOrder order = message.getOrder();
    if (order == null) {
        return;
    }
    console.getOut().println(message);
    if (message.getType() == IMessage.Type.ORDER_CLOSE_OK) {
        if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_SL)) {
            console.getInfo().println(order.getLabel() + " order closed by stop loss");
        } else {
            console.getInfo().println(order.getLabel() + " order closed by other reason than reaching stop loss");
        }
    }

}

OrderClosedBySL

Examples

Manage Stop Loss

Consider a strategy which demonstrates how one can manage Stop Loss of a particular order, namely, by doing the following actions:

  1. add stop loss at order creation,
  2. add stop loss to an existing order,
  3. remove stop loss,
  4. modify stop loss.
package jforex.strategies;

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

/**
 * The strategy demonstrates how one can manage order's stop loss:
 *  1) add stop loss at order creation
 *  2) add stop loss to an existing order
 *  3) remove stop loss
 *  4) modify stop loss
 *
 * The strategy prints all order changes and closes the order on strategy stop
 * 
 */
public class ManageSL implements IStrategy {

    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Instrument")
    public Period period = Period.TEN_SECS;
    @Configurable("Order Command")
    public OrderCommand cmd = OrderCommand.BUY;
    @Configurable("Stop Loss In Pips")
    public int StopLossInPips = 5;

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

    @Override
    public void onStart(IContext context) throws JFException {
        console = context.getConsole();
        history = context.getHistory();
        engine = context.getEngine();

        double lastBidPrice = history.getLastTick(instrument).getBid();

        double amount = 0.001;
        int slippage = 5;
        double stopLossPrice = getSLPrice(lastBidPrice);
        double takeProfitPrice = 0; //no take profit

        //1) make an order with SL at the last bid price
        order = engine.submitOrder("order1", instrument, cmd, amount, lastBidPrice, slippage, stopLossPrice, takeProfitPrice);

    }

    private double getSLPrice (double price){
        return cmd.isLong() 
            ? price - instrument.getPipValue() * StopLossInPips
            : price + instrument.getPipValue() * StopLossInPips;
    }

    private void print(Object o){
        console.getOut().println(o);
    }

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

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if(instrument != this.instrument || period != this.period){
            return;
        }

        //We can change SL only for FILLED and OPENED orders
        if(order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.OPENED){

            //2) if order has no stop loss - we add it
            if (doubleEquals(order.getStopLossPrice(),0)){
                order.setStopLossPrice(getSLPrice(order.getOpenPrice()));
                return;
            }

            //3) if the stop loss increased more than twice - remove it
            if (Math.abs(order.getOpenPrice() - order.getStopLossPrice()) > StopLossInPips * instrument.getPipValue() * 2){
                order.setStopLossPrice(0);
                return;
            }

            //4) increase stop loss by 1 pip
            if (order.isLong()){
                order.setStopLossPrice(order.getStopLossPrice() - instrument.getPipValue());
            } else {
                order.setStopLossPrice(order.getStopLossPrice() + instrument.getPipValue());
            }
        }

    }

    //we need such function since floating point values are not exact
    private boolean doubleEquals(double d1, double d2){
        //0.1 pip is the smallest increment we work with in JForex
        return Math.abs(d1-d2) < instrument.getPipValue() / 10;
    }

    @Override
    public void onMessage(IMessage message) throws JFException {        
        //print only orders related to our order change
        if(message.getOrder() != null && message.getOrder() == order)
            print(message);
    }

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

    //close all active orders on strategy stop
    @Override
    public void onStop() throws JFException {
        for(IOrder o : engine.getOrders()){
            o.close();
        }
    }

}

ManageSL.java

Keep track on Stop Loss changes

Consider a strategy which creates an order on its start and:

  1. On every tick updates the stop loss and take profit levels to keep them in a particular price distance from the current market price.
  2. Draws lines for original SL and TP levels.
  3. Prints a message once the original SL and TP levels get broken.
package jforex.test;

import java.awt.Color;
import java.text.DecimalFormat;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.drawings.IHorizontalLineChartObject;

/**
 * The strategy creates an order on its start and: 
 * 1) On every tick updates the stop loss and take profit levels to keep them 
 *    in a particular price distance  from the current market price.
 * 2) Draws lines for original SL and TP levels.
 * 3) Prints a message once the original SL and TP levels get broken. 
 */
public class SlTpUpdateWithOrigLines implements IStrategy {

    @Configurable("SL and TP price distance")
    public double priceDistance = 0.0003;
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;

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

    private IHorizontalLineChartObject slLine, tpLine;
    private boolean origTpBroken, origSlBroken;

    long lastTickTime = 0;
    DecimalFormat df = new DecimalFormat("0.00000");

    @Override
    public void onStart(IContext context) throws JFException {
        this.context= context;
        engine= context.getEngine();
        history = context.getHistory();
        console = context.getConsole();
        chart = context.getChart(instrument);

        double lastPrice = history.getLastTick(instrument).getBid();
        lastTickTime = history.getLastTick(instrument).getTime();

        double slPrice = lastPrice - priceDistance;
        double tpPrice = lastPrice + priceDistance;
        order = engine.submitOrder("order1", instrument, OrderCommand.BUY, 0.001, 0, 5, slPrice, tpPrice);

        //create lines for original SL and TP levels
        slLine = chart.getChartObjectFactory().createHorizontalLine();
        tpLine = chart.getChartObjectFactory().createHorizontalLine();
        slLine.setPrice(0, slPrice);
        tpLine.setPrice(0, tpPrice);
        slLine.setColor(Color.RED);
        tpLine.setColor(Color.GREEN);
        slLine.setText("Original SL");
        tpLine.setText("Original TP");
        chart.addToMainChartUnlocked(slLine);
        chart.addToMainChartUnlocked(tpLine);

    }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        //we can't update SL or TP more frequently than once per second
        if(instrument != this.instrument || tick.getTime() - lastTickTime < 1000){
            return;
        }
        //bid price has broke either SL or TP - the order has been closed. Or the order has been closed manually;
        if(!engine.getOrders().contains(order)){
            //stop the strategy
            context.stop();
            return;
        }

        order.setStopLossPrice(tick.getBid() - priceDistance);
        order.setTakeProfitPrice(tick.getBid() + priceDistance);

        if(!origSlBroken && tick.getBid() < slLine.getPrice(0)){
            console.getOut().println("Original Stop Loss price level broken, current price: " + df.format(tick.getBid()));
            origSlBroken = true;
        }
        if(!origTpBroken && tick.getBid() > tpLine.getPrice(0)){
            console.getOut().println("Original Take Profit price level broken, current price: " + df.format(tick.getBid()));
            origTpBroken = true;
        }

        lastTickTime = tick.getTime();
    }

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

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

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

    @Override
    public void onStop() throws JFException {
        chart.removeAll();
        for(IOrder o:engine.getOrders()){
            o.close();
        }
    }

}

SITpUpdateWithOrigLines.java

Break even

Consider checking in the strategy if the order profit has reached 5 pips. If this is the case then move the stop loss price to the open price.

@Configurable("")
public int breakEvenPips = 5;

private IOrder order;
private boolean breakEvenReached = false;
//..

@Override
public void onTick(Instrument instrument, ITick tick) throws JFException {
    if(instrument != this.instrument || order.getState() != IOrder.State.FILLED){
        return;
    }
    if( order.getProfitLossInPips() >= breakEvenPips && breakEvenReached == false){
        console.getOut().println("Order has profit of " + order.getProfitLossInPips() + " pips! Moving the stop loss to the open price." );
        order.setStopLossPrice(order.getOpenPrice()); 
        breakEvenReached = true;
    }
}

BreakEven.java

Manually adjustable break even

Consider enhancing the previous example, such that the break even level gets drawn on a chart as a line and the user can adjust the level by manually moving the line on the chart.

@Override
public void onStart(IContext context) throws JFException {

    //..

    //add break even line
    double breakEvenPrice = orderCommand.isLong() 
        ? order.getOpenPrice() + breakEvenPips * instrument.getPipValue()
        : order.getOpenPrice() - breakEvenPips * instrument.getPipValue();

    breakEvenLine = chart.getChartObjectFactory().createPriceMarker("breakEvenLine", breakEvenPrice);
    breakEvenLine.setColor(Color.RED);
    breakEvenLine.setLineStyle(LineStyle.DASH);
    chart.addToMainChart(breakEvenLine);
}
@Override
public void onTick(Instrument instrument, ITick tick) throws JFException {
    if(instrument != this.instrument || order.getState() != IOrder.State.FILLED || breakEvenLine == null){
        return;
    }
    if((order.isLong() && tick.getBid() >= breakEvenLine.getPrice(0)) ||
         (!order.isLong() && tick.getAsk() <= breakEvenLine.getPrice(0))){
        order.setStopLossPrice(order.getOpenPrice());
        chart.remove(breakEvenLine);
        breakEvenLine = null;
    }
}

BreakEvenHLine.java

Break even for multiple orders

Consider applying the same break even logic to multiple orders:

public List<IOrder> orderBeNOTReached = new ArrayList<IOrder>();

@Override
public void onStart(IContext context) throws JFException {
    engine = context.getEngine();
    console = context.getConsole();
    history = context.getHistory();

    submitBeMarketOrder(BUY);
    submitBeMarketOrder(SELL);

}

private void submitBeMarketOrder(IEngine.OrderCommand orderCommand) throws JFException {
    //..
    IOrder order = engine.submitOrder("breakEvenOrder"+counter++, instrument, orderCommand, amount, openPrice, slippage, slPrice, tpPrice);
    orderBeNOTReached.add(order);
}

@Override
public void onTick(Instrument instrument, ITick tick) throws JFException {
    if(instrument != this.instrument 
            || orderBeNOTReached.isEmpty() //all BE's reached, nothing to check
        ){
        return;
    }
    List<IOrder> ordersBeReached = new ArrayList<IOrder>();
    for(IOrder order  : orderBeNOTReached){
        if( order.getProfitLossInPips() >= breakEvenPips){
            console.getOut().println( order.getLabel() + " has profit of " + order.getProfitLossInPips() + " pips! Move the stop loss to break even." );
            order.setStopLossPrice(order.getOpenPrice()); 
            ordersBeReached.add(order);
        }
    }
    for(IOrder order  : ordersBeReached){
        orderBeNOTReached.remove(order);
    }
}

BreakEvenMultipleOrders.java

Trailing step tracking

Consider a strategy which keeps track of stop loss changes according to the set trailing step. The strategy creates two orders in opposite directions such that when one of them will get closed by stop loss, the other one will have its stop loss updated by trailing step. The strategy also counts the number of stop loss updates.

@Configurable("")
public int trailingStep = 10;

private Map<IOrder, Integer> slMoves = new HashMap<IOrder, Integer>();

@Override
public void onStart(IContext context) throws JFException {
    console = context.getConsole();
    engine = context.getEngine();
    history = context.getHistory();

    context.setSubscribedInstruments(new HashSet<Instrument>(Arrays.asList(new Instrument[]{instrument})), true);

    ITick tick = history.getLastTick(instrument);

    IOrder order = engine.submitOrder("buyWithTrailing_"+tick.getTime(), instrument, OrderCommand.BUY, 0.001);
    order.waitForUpdate(2000, IOrder.State.FILLED);     
    slMoves.put(order, 0);
    order.setStopLossPrice(tick.getBid() - instrument.getPipValue() * 10, OfferSide.BID, trailingStep);

    // market BUY for unconditional close
    order = engine.submitOrder("sellWithTrailing_"+tick.getTime(), instrument, OrderCommand.SELL, 0.001);
    order.waitForUpdate(2000, IOrder.State.FILLED);
    slMoves.put(order, 0);
    order.setStopLossPrice(tick.getAsk() + instrument.getPipValue() * 10, OfferSide.ASK, trailingStep);

}

@Override
public void onMessage(IMessage message) throws JFException {
    IOrder order = message.getOrder();
    if (order == null || !slMoves.containsKey(order)) {
        //skip non-order messages and messages not related to our 2 orders
        return;
    }
    console.getOut().println(message);
    if (message.getType() == IMessage.Type.ORDER_CHANGED_OK && message.getReasons().contains(Reason.ORDER_CHANGED_SL)) {
        slMoves.put(order, slMoves.get(order) + 1);
        console.getInfo().format("%s stop loss changed to %.5f\n stop loss moves: %s", order.getLabel(), order.getStopLossPrice(), slMoves).println();
    } else if (message.getType() == IMessage.Type.ORDER_CLOSE_OK){
        slMoves.remove(order);
    }

}

TrailingStepTest.java

Global accounts

There are no Stop Loss/Take Profit orders for global accounts. However one can emulate the logic by keeping the Stop Loss Price level in a strategy and closing the position by creating an opposite direction market order. Consider the following strategy:

@Override
public void onTick(Instrument instrument, ITick tick) throws JFException {
    //add trailing stops
    for (IOrder order : engine.getOrders(instrument)) {
        if (!(order.getState() == IOrder.State.FILLED && Double.compare(order.getRequestedAmount(), order.getAmount()) == 0) // instrument's position
                || (slPrices.get(order) == null && !addSLToExisting)) {
            continue;
        }
        double prevSl = slPrices.get(order) == null ? 0 : slPrices.get(order);
        double marketPrice = order.isLong() ? tick.getBid() : tick.getAsk();
        int sign = order.isLong() ? +1 : -1;
        double slInPips = Math.abs(marketPrice - prevSl) / instrument.getPipValue();
        if (slInPips > trailingStop + trailingStep) {
            double newSl = marketPrice - (sign * trailingStop * instrument.getPipValue());
            slPrices.put(order, newSl);
            log("%s of %s moved SL %.5f -> %.5f", order.getLabel(), order.getInstrument(), prevSl, newSl);
            if(drawSl){
                for(IChart chart : context.getCharts(instrument)){
                    IPriceMarkerChartObject line = chart.getChartObjectFactory().createPriceMarker(order.getId(), newSl);
                    line.setText("SL for " + order.getId());
                    line.setColor(Color.GRAY);
                    line.setLineStyle(LineStyle.FINE_DASHED);
                    chart.add(line);
                }
            }
        }
    }

    //check if SL levels are reached
    Iterator<Entry<IOrder, Double>> entries = slPrices.entrySet().iterator();
    while (entries.hasNext()) {
        Map.Entry<IOrder, Double> entry = entries.next();
        IOrder order = entry.getKey();
        double slPrice = entry.getValue();
        if(order.getInstrument() != instrument){
            continue;
        }

        double marketPrice = order.isLong() ? tick.getBid() : tick.getAsk();
        if((order.isLong() && slPrice >= marketPrice) || (!order.isLong() && slPrice <= marketPrice)){
            log("%s of %s breached SL level of %.5f (last %s=%.5f), creating an oposite direction Market order to close the position", 
                    order.getLabel(), 
                    order.getInstrument(), 
                    slPrice, 
                    order.isLong() ? "BID" : "ASK",
                    marketPrice
                );
            engine.submitOrder("OppDirOrder_"+System.currentTimeMillis(), instrument, order.isLong() ? OrderCommand.SELL : OrderCommand.BUY, order.getAmount());
            entries.remove();
            if(drawSl){
                for(IChart chart : context.getCharts(instrument)){
                    chart.remove(order.getId());
                }
            }
        }
    }
}

TrailingStopGlobalStrategy.java

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