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:
- On submitting a main order with nonzero Stop Loss and Take Profit prices, both Stop Loss and Take Profit orders automatically get created.
- 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).
- 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:
- Order must be in state FILLED or OPENED.
- Stop Loss price must be divisible by 0.1 pips, otherwise the order will be rejected.
- 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");
}
}
}
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:
- add stop loss at order creation,
- add stop loss to an existing order,
- remove stop loss,
- 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();
}
}
}
Keep track on Stop Loss changes
Consider a strategy which creates an order on its start and:
- On every tick updates the stop loss and take profit levels to keep them in a particular price distance from the current market price.
- Draws lines for original SL and TP levels.
- 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();
}
}
}
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;
}
}
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;
}
}
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);
}
}
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);
}
}
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