# Labeling Opportunities in Price Series

One approach to trading which has been puzzling me lately, is to sit and wait for opportunities. 🙂 Sounds simplistic, but it is indeed different than, for instance, the asset allocation strategies. In order to be able to even attempt taking advantage of these opportunities, however, we must be able to identify them. Once the opportunities are identified – we can try to explain (forecast) them using historical data.

The first step is to define what an opportunity is. It could be anything, for instance we can start with a strong directional move, but then we need to define what a strong directional move is. Let’s give it a try.

We can define a strong directional move as a 5% move over 3 days for instance. However, that’s not likely to work for different markets (think futures). 5% move is a lot in treasuries, but not so much in oil. The solution is to normalize the returns using volatility. I will use exponential moving average for that.

Here is a short code snippet:

```good.entries = function(ohlc, min.days=3, days.out=15, vola.len=35, days.pos=0.6, stop.loss = 1.5) {
stopifnot(days.out > min.days)

hi = Hi(ohlc)
lo = Lo(ohlc)
cl = Cl(ohlc)

rets = ROC(cl, type='discrete')
arets = sqrt(EMA(rets * rets, n=vola.len))
erets = EMA(rets, n = vola.len)

res = rep(0, NROW(ohlc))
days = rep(0, NROW(ohlc))
longs = rep(0, NROW(ohlc))
shorts = rep(0, NROW(ohlc))

for(ii in min.days:days.out) {
hh = lag.xts(runMax(hi, n=ii), -ii)
ll = lag.xts(runMin(lo, n=ii), -ii)
hi.ratio = (hh/cl - 1)/erets
lo.ratio = (ll/cl - 1)/erets

# days required
dd = ceiling(days.pos*ii)

longs = hi.ratio > dd & -lo.ratio < stop.loss
longs = ifelse(!is.na(longs) & longs == 1, 1, 0)

shorts = -lo.ratio > dd & hi.ratio < stop.loss
shorts = ifelse(!is.na(shorts) & shorts == 1, -1, 0)

both = ifelse(longs == 1, 1, shorts)
new.days = ii*as.numeric(res == 0 & both != 0)
res = ifelse(res != 0, res, both)
days = ifelse(days != 0, days, new.days)
}

full.df = data.frame(data = index(ohlc), entry = res, days = days, erets = erets, arets = arets)
oppo.df = full.df[!is.na(full.df\$entry) & full.df\$entry != 0, ]

return(list(full=full.df, oppo=oppo.df))
}}
```

The code basically defines an opportunity as a directional move over X days (X is between 3 and 15). The minimum move we would accept is computed by taking a percentage of the days (60% by default), rounding that number up, and multiplying it by the standard deviation of the normalized returns. We also require that the move in the opposite direction is smaller. Hard to explain, but hopefully you get the idea.

The function returns a list of two data frames – one for all days, and one with the opportunities only. There is a label assigned to each day – 0 by default. The opportunities are the 1 and -1 days of course.

Is this trade-able? Absolutely. When an opportunity arises, we enter in the suggested direction. The exit is a profit target, we can use a stop loss of the same magnitude.

Why all this effort again? Simple – these labels can be used as the response, or the dependent variable in an attempt to explain (forecast) the opportunities using historical data. At this point what’s left is to generate what we think might constitute a good set of predictors, align the two sets appropriately (no data snooping) and the rest is standard machine learning.

That’s for another post though.

1. Patrick Wallace says:
1. quintuitive says:
2. ntguardian says: