ALEXANDER KAHL

A big emphasis in algorithmic trading is put on finding the right entry and exit signals for trades. Especially in the field of technical analysis, buy and sell signals are too often based solely on the evaluation of indicators or combinations thereof. When it comes to entry and exit signals, the latter takes an even more crucial role in adverse situations.

In this blog post, we focus on getting out of trades, i.e. closing positions. We cover interesting topics related to our new and improved Order API and put everything together in a simple trading strategy using the Trality Code Editor. All Python code is explained in a step-by-step fashion.

## Why worry about exit rules?

Setting a decision rule to exit trades is at least as important as timing your entry points. Generally speaking, we can distinguish between two broad categories for exit signals:

• signal driven exits: closing a position is purely driven by some sort of trade signal, i.e. evaluation of an indicator, cross overs or other model signals.
• profit / time-related exits: closing a position is based on some (risk-adjusted) profitability measure and/or a time-related component (e.g. close after X days).

Although these two crudely defined categories are by no means mutually exclusive, they are good enough for us to be used as a distinction in this blog post.

The main issue with purely signal-based exits is that traders might not be willing to accept huge swings in their profit and loss account until they receive an exit signal. Hence most algorithmic trading strategies include some sort of stop limit on profits as well as losses. These limits are very often also tied to some risk measure (e.g. volatility). Besides, some investment strategies might also introduce a maximum holding period for a given position.

In light of the importance of stop limits in an algorithmic trading context, we'll focus on implementing profit / loss and time-related exits using the new Trality Order API.

## Tripple-Barrier Method

The Tripple-Barrier Method introduced by M. Lopez de Prado in Advances in Financial Machine Learning, is a labelling method for financial time-series. It can be used in supervised machine learning algorithms to predict the sign of the return on the first barrier touched.

Please note that in subsequent sections we are not using Machine Learning to estimate the labels ourselves, but instead use this method as a decision rule for exiting our trades.

To get a better understanding of this method we can visualize it graphically.

As the name of the Tripple-Barrier-Method suggests, we are looking at three boundaries:

• take-profit-barrier: topmost horizontal dashed line
• stop-loss-barrier: bottommost horizontal dashed line
• time-barrier: dashed vertical line to the right (max-holding period)

To see how easily this can be implemented using our Python API, we disentangle this exit rule step by step. First of all, we look at if-touched market orders, which will serve as the key order type for the barrier method. Secondly, we explain the concept of Order Scope, which enables us to chain orders together. Last but not least, we show you how this can be used in a simplified trading bot.

## Sending stop orders

In our new order API we have tried to simplify order creation for you as much as possible. You can send all commonly available order types, such as market, limit, if-touched and trailing orders. Besides, most order types come with 3 quantity configuration types. For a more detailed description and much more functionality, please visit the Trality Documentation. We can now look at the key order type to implement our exit rule.

### If-touched Market Order

An if-touched market order is a conditional order. It triggers a Market Order if a given stop price is reached or crossed. The direction of crossing is resolved at order creation using the current market price.

To simplify this, imagine the following situation:

Let's assume the current market price for BTC is around 8840 USDT and we want to sell 3 BTC at 9000 USDT. We could do so by using an if-touched market order in the following way:

order_iftouched_market_amount("BTCUSDT",amount=-3,stop_price=9000)

This is already helpful, but in a profit-and-loss setting it seems more natural to specify stops in percentage terms. Therefore, we have created two easy-to-use wrapper functions for you.

Take Profit

Creates an If-Touched Market sell order for a given amount with a stop_price that lies stop_percent above the current market price (take-profit).

order_take_profit(symbol="BTCUSDT",amount=3,
stop_percent=0.05,subtract_fees=False)

Stop Loss

Creates an If-Touched Market sell order for a given amount with a stop_price that lies stop_percent below the current market price (stop-loss).

order_stop_loss(symbol="BTCUSDT",amount=3,
stop_percent=0.05,subtract_fees=False)
Note, that both of these convenience functions have a parameter subtract_fees. Setting it True will automatically deduct exchange fees form the amount.

Now that we know how to send orders that will be executed once a specific price is touched or breached we need a way to link orders together.

## Order Scope

With the concept of order scope we offer you the possibility to link orders together and thereby control their execution.

At Trality, we currently provide you with two different kinds of Order Scopes:

• Sequential: orders created within this scope are executed strictly one after another.
• One Cancels Other: orders created with this scope are cancelled as soon as one of the linked orders is filled.

## Setup horizontal barriers

The "One Cancels Other" order scope is exactly what we need for our tripple-barrier exit method.

For the moment, we'll ignore the vertical barrier and see how we can implement a suitable function to handle our profit and loss-taking barriers. The goal is to obtain a simple function that creates this "double-barrier" for a given symbol, the amount and upper as well as lower barrier. Let's see how we can do this in Python:

def make_double_barrier(symbol,amount,take_profit,stop_loss,state):

"""make_double_barrier

This function creates two iftouched market orders with the onecancelsother
scope. It is used for our tripple-barrier-method

Args:
amount (float): units in base currency to sell
take_profit (float): take-profit percent
stop_loss (float): stop-loss percent
state (state object): the state object of the handler function

Returns:
TralityOrder:  two order objects

"""

with OrderScope.one_cancels_others():
order_upper = order_take_profit(symbol,amount,
take_profit,
subtract_fees=True)
order_lower = order_stop_loss(symbol,amount,
stop_loss,
subtract_fees=True)

if order_upper.status != OrderStatus.Pending:
errmsg = "make_double barrier failed with: {}"
raise ValueError(errmsg.format(order_upper.error))

# saving orders
state["order_upper"] = order_upper
state["order_lower"] = order_lower
state["created_time"] = order_upper.created_time

return order_upper, order_lower


As we can see this method uses the given input information to create our take-profit and stop-loss barriers and stores relevant information in the state object of our bot.

Our order scope ensures that if one order is filled the other is canceled immediately. There's no need to keep track of the individual orders because cancelation happens automatically.

## Adding a maximum holding period

As an additional feature we can include a maximum holding period (i.e. vertical barrier). This method just uses the state information to check if our position is held longer than our maximum holding period. If that is the case, we close the position and cancel our barrier orders.

def check_max_holding_period(timestamp,state):

"""check_max_holding_period

This function checks the for a first touch in the vertical barrier.
If the vertical barrier is touched the double barrier orders are canceled.

Args:
timestamp (float): milliseconds of current engine time
state (state object): the state object of the handler function

Returns:
bool value

"""

if check_state_info(state) is None:
return True

time_delta = timestamp - state["created_time"]

if state["max_period"] is None:
return True

# cancel order if vertical barrier reached
if time_delta >= state["max_period"]:
print("vertical barrier reached")
cancel_order(state["order_upper"].id)
cancel_order(state["order_lower"].id)
close_position(state["order_lower"].symbol)

return True


In the above example, we use a simple function to check our state object for all required information.

def check_state_info(state):

"""check_state_info

This function checks the state object for relevant order information.
If the information exists it also refreshes the order_upper from the
double barrier function

Args:
state (state object): the state object of the handler function

Returns:
None or TralityOrder order_upper

Raises:

AssertionError: If invalid order specification.

"""

if "order_upper" not in state.keys():
return None
elif state["order_upper"] is None:
return None

order_upper = state["order_upper"]

errmsg = "No max_period in state. Unable to check max holding period"

assert "max_period" in state.keys() , errmsg
# refreshing order from api
order_upper.refresh()

if order_upper.status != OrderStatus.Pending:

# resetting state information
state["order_upper"] = None
state["order_lower"] = None
state["created_time"] = None
return None

return order


## Putting it all together

We are now ready to use what we have developed in order to package everything in a simple trading strategy on the pair BTCUSDT trading in 15min intervals. First let's define a simple entry rule for our bot.

### Our Entry rule

We define a price signal and a volume signal. If both are true we go long BTCUSDT.

1) Price signal
The last five consecutive close prices tick upwards (we call it upticks):

$$\text{upticks} = \sum_{t = T-5}^{T} sign(close_t - close_{t-1})$$

Hence our price signal will be true if upticks == 5. Yes, this is easy. We can write a little helper function:

def last_five_up(data):
prices = data.select("close")
signs = np.sign(np.diff(prices))[-5:]
return sum(signs) == 5

2) Volume signal
We define our volume signal such that:

$$ema(volume,20) >ema(volume,40)$$

As usual we can use the data object directly:

ema_short_volume, ema_long_volume = volume.ema(20).last, volume.ema(40).last

# return early on missing data
if ema_short_volume is None:
return False

has_high_volume = ema_short_volume[-1] > ema_long_volume[-1]

### Exit Rule

According to the previous elaboration, we use our tripple-barrier method with take-profit of 5% and stop-loss of 3%. For a first try, we set the maximum holding period to "None," which will just ignore the parameter.

### Handler Function

Finally, we are ready to code our handler function and put the entire algorithm together. We commit 95% of the capital to our entry signals:


def initialize(state):
state.max_period = None # exclude vertical

@schedule(interval="15min", symbol=["BTCUSDT"], window_size=200)
def handler(state, data):

# moving averages on volume
volume = data.volume
ema_short_volume, ema_long_volume = volume.ema(20).last, volume.ema(40).last

# return early on missing data
if ema_short_volume is None:
return False

has_high_volume = ema_short_volume[-1] > ema_long_volume[-1]

# at every timestamp check max holding period
check_max_holding_period(get_timestamp(),state)

# getting portfolio and position information
portfolio = query_portfolio()
position = query_open_position_by_symbol(data.symbol)
has_position = position is not None

if not has_position and last_five_up(data) and has_high_volume:
price = data.close_last

# setup barriers
0.05,0.03,state)



### Sample Backtest

Just for illustration purposes, we'll run the bot for one month in January 2020 to illustrate the concept. This is by no means an elaborate analysis of strategy performance – only a simple example.

Even if this period is not at all representative, we can see that some drawdowns could be avoided due to our take-profit and stop-loss barrier. To see this we look at our positions:

Please be aware that our backtesting system fills if-touched orders precisely at the respective stop price. This simplification cannot be guaranteed in live trading.

Of course this is an over-simplification. If we look at our entry and exit points, we can already detect room for improvement:

In this case, we quite lucky. After most exits, the price really drops. However, if our take-profit barrier is triggered, then our entry rule could be triggered again right after it. In the worst case scenario, we could enter right before a steep drop in price.

### Summary

In this blog post, we show alternative exit rules that have more real-world character and are more closely related to our natural risk-aversion towards financial losses versus gains. The focus here has been to acquaint you with our new order API and show you how it can be used.

Disclaimer: None of what is found in this article should be considered investment advice. The above article is merely an opinion piece and does not represent any kind of trading advice or suggestions on how to invest, how to trade, in which assets to invest in or suggestions on how trading bots or trading algorithms can or should be used! Always do your own research before investing and always (!) only invest what you can afford to lose! Backtests are not indicative of future results.