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.
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.
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.
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.
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.↩︎
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.↩︎