The early profit taker and the early loss taker are both variations of a more general rule: the A and B system. For the profit taker I use \(A=5\) and \(B=20\). The loss taker has these values reserved, \(A=20\) and \(B=5\). The systematic stop loss rule used in a proprietary trading crypto desk is based on the stop loss component of the A and B system below. I have included the specification and code here to satisfy the curious.

1 Specification

Parameters A and B: For a given variation you need two parameters, \(A\) and \(B\).

Standard position size: A standard position size is $100,000, dividend by the instrument value volatility1 measured in dollars at the time you take on your position.

Initial position: You go long one standard position size.

Deviation: A deviation is one daily standard deviation of reutrns, as measured at your last entry point. Note that we use volatility in price points, not percentage points as normal. The volatility in price points is equal to the percentage point volatility, multiplied by the current price.

Profit target when long: If the price rises by more than \(A\) deviations from your last entry price, then you should sell out and go short one standard position size (reverse your position). You are always long or short.

Trailing stop loss when long: If the price of the instrument falls by more than \(B\) deviations from its high since you entered; then sell and go short one standard position size.

On reversal: When you reverse your position re-measure the deviation level to set your stop loss and proft target levels, and to deterimne the new standard position size.

Profit target when short: When currenctly short you would generate a profit taking trade when the price falls by more than \(A\) deviations from your last entry price; and then go long.

Stop loss when short: You will generate a stop loss trade when a price rises by more than B deviations from its low, and then reverse your position by going long.

2 Code

This is the code for a complete position management system, not just a trading rule, since it tells us exactly what size positions to take. From the code above (click code) you can probably see that the system does position sizing correctly, but for an arbitrary volatility target on a single instrument.

profit.loss.taker.f
function (prices, entry_date = as.Date("2007-01-05"), MA_window_size = 25, 
    A_factor = 20, B_factor = 5) 
{
    k = match(entry_date, prices$Date) - 1
    prev_l = list(Position = 0, Price = 0, Profit_Target = 0, 
        Stop_loss = 0, A_deviation = 0, B_deviation = 0, Last_entry = 0, 
        Cum_profit = 0)
    prices %>% mutate(Return = Price - lag(Price), `Std. Deviation` = Rolling.volatility.f(Return, 
        MA_window_size, k), Position = 0, `A * deviation` = 0, 
        `B * deviation` = 0, `Last entry` = 0, `Profit target` = 0, 
        `Stop loss` = 0, `Entry position` = 1e+05/`Std. Deviation`, 
        Profit = 0, `Cum profit` = 0) %>% pmap_dfr(function(...) {
        out <- list(...)
        if (out$Date > entry_date) {
            out$Position = nxt.position.f(prev_l$Position, prev_l$Price, 
                prev_l$Profit_Target, prev_l$Stop_loss, out$`Entry position`)
            if (out$Position == prev_l$Position) {
                out$`A * deviation` = prev_l$A_deviation
                out$`B * deviation` = prev_l$B_deviation
                out$`Last entry` = prev_l$Last_entry
            }
            else {
                out$`A * deviation` = out$`Std. Deviation` * 
                  A_factor
                out$`B * deviation` = out$`Std. Deviation` * 
                  B_factor
                out$`Last entry` = out$Price
            }
            out$Profit = out$Return * prev_l$Position
            out$`Cum profit` = out$Profit + prev_l$Cum_profit
            if (out$Position > 0) {
                out$`Profit target` = out$`Last entry` + out$`A * deviation`
                out$`Stop loss` = out$`Last entry` - out$`B * deviation`
            }
            else {
                out$`Profit target` = out$`Last entry` - out$`A * deviation`
                out$`Stop loss` = out$`Last entry` + out$`B * deviation`
            }
        }
        else if (out$Date == entry_date) {
            out$Position = out$`Entry position`
            out$`A * deviation` = out$`Std. Deviation` * A_factor
            out$`B * deviation` = out$`Std. Deviation` * B_factor
            out$`Last entry` = out$Price
            if (out$Position > 0) {
                out$`Profit target` = out$`Last entry` + out$`A * deviation`
                out$`Stop loss` = out$`Last entry` - out$`B * deviation`
            }
            else {
                out$`Profit target` = out$`Last entry` - out$`A * deviation`
                out$`Stop loss` = out$`Last entry` + out$`B * deviation`
            }
            out$Profit = 0
            out$`Cum profit` = 0
        }
        else {
            out$`Entry position` = NA
            out$Position = NA
            out$`A * deviation` = NA
            out$`B * deviation` = NA
            out$`Last entry` = NA
            out$`Profit target` = NA
            out$`Stop loss` = NA
            out$Profit = NA
            out$`Cum profit` = NA
        }
        prev_l <<- list(Position = out$Position, Price = out$Price, 
            Profit_Target = out$`Profit target`, Stop_loss = out$`Stop loss`, 
            A_deviation = out$`A * deviation`, B_deviation = out$`B * deviation`, 
            Last_entry = out$`Last entry`, Cum_profit = out$`Cum profit`)
        out
    })
}

nxt.position.f
function (prev_pos, prev_price, prev_profit_target, prev_stop_loss, 
    next_entry_position) 
{
    out = ifelse(prev_pos > 0, ifelse((prev_price > prev_profit_target | 
        prev_price < prev_stop_loss), -next_entry_position, prev_pos), 
        ifelse((prev_price < prev_profit_target | prev_price > 
            prev_stop_loss), next_entry_position, prev_pos))
    return(out)
}

Rolling.volatility.f
function (ret, MA_window_size = 25, k = 3) 
{
    out = data.table::frollapply(ret, MA_window_size + 1, sd, 
        na.rm = T)
    out[is.na(out)] = sapply(1:MA_window_size, function(i) sd(ret[1:i], 
        na.rm = T))
    out[1:k] = NA
    return(out)
}

Here are some interesting characteristics of the A and B system:

If A is larger than B: The system will tend to hit stop losses more frequently, but when trends occur it will exploit them. This is the case for the early loss taker.

If B is larger than A: The system will take profits frequently but suffer losses on large adverse movements. This will be profitable if prices remain range bound. This is how the early profit taker behaves.

Larger values of B: Will mean that slower trends will be more profitable than faster ones.

Smaller values of B: The system will exploit faster trends better.

3 Example

In this example I mimics someone closing positions that have started to lose money. The back-test below shows how profitable this rule would have been if it had been run in the past. By using stop losses we are trading somewhat like an early loss taking trend follower.

profit.loss.taker.f( # an 'early loss taker' based on the 'A and B' system defined
  # with parameters A=20 and B=5.
  dat, 
  entry_date=as.Date("2019-01-05"), 
  MA_window_size=25, A_factor=20, B_factor=5
) %>%
  arrange(desc(Date)) %>% 
  format.dt.f(., ron_vars=names(.)[-1])

I am a big fan of trend following rules. Firstly, they work. There is a lost of academic research and real performance statistics supporting the use of trend following rules. Secondly, I like rules which I can explain. Also, trend following is an easy to implement trading strategy using only pricing data. And finally, it has bening positive skew 2.


  1. The instrument currency volatility is the expected standard deviation of daily returns from owning one instrument block in the currency of the instrument. If one of an instrument goes up in price by \(1\)% how much do we gain or lose? this is the block value.↩︎

  2. Market followers tend to see positive skew from taking small losses as the trend goes against them, with an occasional large profit from a significant move in their favour. Conversely contrarians see negative skew, with many small profits as each mispricing is corrected, then occasional large losses when prices jump away from their equilibrium.↩︎