Tuesday, May 26, 2020

Algorithms for Trading in a Volatile Market

The updated version of the script in this discussion can always be found on Trading View.

I venture off into many hobbies, everything from running and trying to complete a mile in less than five minutes, to building a bike because I watched one-too-many Youtube videos of people converting old bikes into cafe racers. Most of my hobbies start that way, on Youtube, watching people trying to do things that seem a little hard to achieve but fun at the same time.

The COVID pandemic brought a pretty cool opportunity to dive into the stock market. Some however would argue that this is the worst time to dive into such a hobby, but I disagree... So I dove deep into derivatives and equities alike and have found a lot of really cool stuff.

The derivatives game is hilarious, as it resembles gambling so closely that even researching my moves makes me feel like I'm sitting at the blackjack table, trying to count my way to a twenty-one.

So, for now, let's roll with just equities, mostly as a day-trader.

Day Traders don't necessarily trade on "information", is WHAT I THINK? If you're trading on "information", I don't consider that day-trading, no matter how short your hold is... To me, that's a good investment, backed by (hopefully) solid data points. For a position to be considered a day-trade, I think it has to be opened and closed based mostly on indicators.

In my previous post (Doubting my vibe), I dove into missing a trade because all the "indicators" pointed to opening that trade, and I didn't. Here's what that trade looked like, and not only that, my screen for the last two months has looked similar to this... consisting of the same indicators and graphs, in the very same order, you see here.




Here's what this picture shows;
  1. The first graph shows the Price trend as captured by the blue line, along with it's Exponential Move Average (EMA) highlighted by the red line.
  2. The second graph shows the Relative Strength Index (RSI) for Close in the purple line.
  3. The third shows whether the market is trying to enter Squeeze (will be highlighted by a yellow circle when the market does enter a squeeze). It also has cycles of Green/Red to show general buying/selling levels of the said stock.
  4. The last graph contains a customized formula to adjust the EMA of HLC3 which is equal to (High + Low + Close) /3. The script below will contain this formula as authored by LazyBear on TradingView and slightly modified to fit my use cases. In this graph, the purple line is the equivalent of the tci variable from later on in this article.
After playing with the previous a bit, and being up thousands in a few trades, and then down thousands, I realized there was a huge flaw in my strategy. I was at best following one indicator that would highlight an entry point, and ignoring the two others that didn't come to the same conclusion. After digging deeper into Youtube to find what I was doing, I discovered that this happens quite a bit and it's just part of the game. It wasn't just me, but there are a lot of people reading things the wrong way and taking "leaps of faith" hoping for a return.

So, to eliminate (or to minimize) these false entry/exit (ENEX) points, I began writing a script of my own to plot points on the Price trend line, that will show entry and exit points for a said trade. It will take into account all of the previously mentioned indicators, and come to a conclusion with a certain confidence level assigned to it. (For a long position) The script has five possible conclusions for each Price point;
  1. Black - Do nothing (sometimes the hardest decision is to put everything away and wait)
  2. White - Entry, slightly confident
  3. Maroon - Exit, slightly confident
  4. Lime - Entry, reasonably confident
  5. Red - Exit, reasonably confident
As an example, to minimize risk and maximize return, one should always enter on a Lime and exit on a Red. But logic dictates that such ENEX points are rare, and of course present a great opportunity to initiate a position.

Let's begin with the script.

Script Part I - Key Variables
Purpose: Establishing expectations for today

The very first portion of the script begins with a few key variables for the day in question. These consist of Open, High, Low for the most recent day (let's call it Today), and the six (6) days of open market preceding Today.

study(title="Buy/Sell Indicator", shorttitle="BS_IND", overlay=false)

// TODAY.
today_open = security(tickerid, 'D', open[0])
today_high = security(tickerid, 'D', high[0])
today_low = security(tickerid, 'D', low[0])
plot_this_val = close[0] > today_open ? high[0] : low[0]

// 6 DAY OPEN.
p1_open = security(tickerid, 'D', open[1]), p2_open = security(tickerid, 'D', open[2]), p3_open = security(tickerid, 'D', open[3]), 
p4_open = security(tickerid, 'D', open[4]), p5_open = security(tickerid, 'D', open[5]), p6_open = security(tickerid, 'D', open[6])

// 6 DAY HIGH.
p1_high = security(tickerid, 'D', high[1]), p2_high = security(tickerid, 'D', high[2]), p3_high = security(tickerid, 'D', high[3]), 
p4_high = security(tickerid, 'D', high[4]), p5_high = security(tickerid, 'D', high[5]), p6_high = security(tickerid, 'D', high[6])

// 6 DAY LOW.
p1_low = security(tickerid, 'D', low[1]), p2_low = security(tickerid, 'D', low[2]), p3_low = security(tickerid, 'D', low[3]), 
p4_low = security(tickerid, 'D', low[4]), p5_low = security(tickerid, 'D', low[5]), p6_low = security(tickerid, 'D', low[6])

// 6 DAY PUSH UP - OPEN to HIGH.
p1_ophi = (p1_high/p1_open), p2_ophi = (p2_high/p2_open)
p3_ophi = (p3_high/p3_open), p4_ophi = (p4_high/p4_open)
p5_ophi = (p5_high/p5_open), p6_ophi = (p6_high/p6_open)

// 6 DAY PULL BACK - OPEN to LOW.
p1_oplo = (p1_low/p1_open), p2_oplo = (p2_low/p2_open)
p3_oplo = (p3_low/p3_open), p4_oplo = (p4_low/p4_open)
p5_oplo = (p5_low/p5_open), p6_oplo = (p6_low/p6_open)

These variables are then used to calculate the average directional move for the price, both towards the High and the Low points. Meaning, after the opening bell, by how much (in %) does the stock typically move up to set a high point, and down to set a low point. 

// 6 DAY DIRECTIONAL MOVE AVERAGES.
average_push_up = ((p1_ophi + p2_ophi + p3_ophi + p4_ophi + p5_ophi +                       p6_ophi)/6)-1
average_pull_down = ((p1_oplo + p2_oplo + p3_oplo + p4_oplo + p5_oplo +                        p6_oplo)/6)-1

This part is extremely important as it helps set boundaries (for Today) in which we can comfortably trade... For example, if the stock falls by more than what has been an expected decline from the opening bell, then we know not to venture into a trade without more information because the indicators alone simply do not capture the full picture of what is going on (e.g. there may be severe bad news leading the market to overreact via a massive dump of company stock). These boundaries are often thought of as Support or Resistance. Support points occur when the price has dropped too low and a market is due for a correction upwards as it does not think the stock price is valued fairly at that level. The reverse is true for Resistance points.

// TODAYS EXPECTATIONS FOR HIGH/LOW.
resistance = today_open * (average_push_up+1)
support = today_open * (average_pull_down+1)

Thus, for a long position, these boundaries help us minimize our risk by neither buying above the Resistance point (upper boundary), nor buying below the Support point (lower boundary), no matter how tempting it may be - especially in the latter case.

We can now plot all these values.
// PLOT BOUNDARIES.
plot(series=today_high, style=line, color=green, linewidth=1, transp=0)
plot(series=resistance, style=line, color=#c0c0c0, linewidth=1, transp=0)
plot(series=support, style=line, color=#c0c0c0, linewidth=1, transp=0)
plot(series=today_low, style=line, color=maroon, linewidth=1, transp=0)

This sets the stage to move on to the next section.

Script Part II - Indicators (RSI & EMA of Close)
Purpose: Prerequisite for Lime, Maroon and Red points

After establishing expectations for Today, it's only reasonable to begin approaching Today's data and understand what is happening with each passing minute after 9:30 EST.

And so we begin by analyzing the RSI level of the most recent close (in other words, the latest price of the stock). We then take this RSI level and establish its EMA to see if the price of the stock has tumbled far away from what was expected to happen. The gap between the RSI and its EMA is captured by rsi_ema_lvl.

// PRIMARY INDICATORS.
rsi_lvl = rsi(close, 25)
ema_lvl = ema(rsi_lvl, 25)
rsi_ema_lvl = ema_lvl/rsi_lvl-1

So far, we've taken into account Boundaries via Supports/Resistance points and Indicators via RSI/EMA of the latest closing price. These will help significantly in plotting Lime, Maroon, and Red points, from which Lime and Red points are extremely rare because the opportunity to enter and exit a trade with complete confidence is rare in itself; the truth is the downside is always there. This is why we need to take this one step further by analyzing indicators a bit more closely to plot White points to indicate slightly confident entries.

Script Part III - Indicators (EMA of HLC3)
Purpose: Prerequisite for White points

Setting strict standards on ENEX points provides us with fewer (but better) opportunities to make money and that is good for someone with a low-risk tolerance. But to make our ENEX more frequent (and thus embracing a higher risk-reward ratio by association), we have to begin with the following;

adjusted_recent_close = hlc3 

Here, the keyword/variable is HLC3. Instead of taking the Closing price (of any given period) for face-value, we look at the period and consider three things, High, Low, and Closing equally. We then add their respective values and divide them by 3 to find their average. This gives us HLC3 (or adjusted_recent_close), and in TradingView, it's a built-in variable.

We then find the EMA of this adjusted_recent_close, over the past 25 periods, and store its value in ema_arc. This looks like the following;

n1 = input(25, "Channel Length")
ema_arc = ema(adjusted_recent_close, n1)

From this point onward, the EMA function is utilized for its formula to smooth out inputs.

The next step is to find the difference between adjusted_recent_close and ema_arc, to find how much the price has deviated from expectations, and then smooth that difference by using the EMA function. This gives us a smoothened EMA value of the deviation in price from expected (as indicated by EMA - Expected Moving Average) and actual (as indicated by adjusted_recent_close). We simply call this variable d, for deviation.

d = ema(abs(adjusted_recent_close - ema_arc), n1)

Now that we have a smoothened deviation, we can establish the last bit of our logic before finally plotting points on the Price trend line.

n2 = input(25, "Average Length")
ci = (adjusted_recent_close - ema_arc) / (0.015 * d)
tci = ema(ci, n2)

We finalize this by looking at the second half of the equation for the ci variable. Here we multiply d by 0.015. Why 0.015? Purely personal preference, as the number captures how big a deviation is enough to make an ENEX call. If one wants a higher deviation between the expected and actual price, then they must also accept the fact that there will be fewer ENEX points. A 1.5% deviation is reasonable enough for me to enter and exit a trade.

We then divide the difference between adjusted_recent_close and ema_arc by our deviation and our preferred constant. The result is a ci value from a range of 100 to -100, where closer to 100 represents overbought (overvalued) levels and thus an exit point, and closer to -100 represents oversold (undervalued) levels and thus an entry point.

Once again we use the EMA formula to smooth out our ci variable and store its value in tci.

Script Part IV - ENEX Points
Purpose: Time to plot our ENEX points

To plot our points, we need to give every point one of five colours we mentioned above. The logic for that is as follows;

indicator_color = (rsi_ema_lvl > average_push_up) 
    ? (rsi_lvl < ema_lvl 
        ? (rsi_lvl > 50 
            ? black 
            : ((low[0] < support*0.985 or low[0] == today_low) 
                ? black 
                : (tci < -50 
                    ? (rsi_lvl < 30 
                        ? lime
                        : white
                    ) 
                    : black
                )
            )
        ) 
        : black
    ) 
    : (rsi_ema_lvl < average_pull_down ? ((rsi_lvl > 70) ? red : maroon) : black)

Below is the essence of the above IF conditions;
  • If the ema_lvl is higher than the rsi_lvl by more than an average_push_up, then there is an expected increase in buying as it has been below the expected levels. Thus an opportunity to buy.
    • If rsi_lvl is less than ema_lvl, then do one of the following:

      • If rsi_lvl is above 50, do nothing (as it may be getting closer to overbought - thus overvalued)

      • If rsi_lvl is below 50, then do one of the following

        • If the current price is more than 1.5% below the support point then do nothing - because the price has fallen much lower than we'd expected

        • If the current price is higher than 1.5% below the support, then do one of the following

          • If tci is below -50, then do one of the following

            • If rsi_lvl is below 30 then it's oversold (undervalued) and thus a reasonably confident entry point
            • If rsi_lvl is above 30 then it's still oversold, but only a slightly confident entry point

          • If tci is above -50, do nothing

    • If rsi_lvl is more than ema_lvl then do nothing
  • If the ema_lvl is lower than the rsi_lvl by more than an average_push_down, then there has been more buying than expected, and thus an opportunity to sell.
    • If the RSI is above 70, it's a reasonably confident exit point
    • If the RSI is below 70, it's a slightly confident exit point
The indicators above are finally plotted with the following;

// PLOT BUY/SELL INDICATORS.
plot_this_val = close[0] > today_open ? high[0] : low[0]
plot(series=plot_this_val, style=cross, color=indicator_color, linewidth=2, transp=0)

And now, we have an updated graph which looks like the following;
  1. First graph
    1. resistance and support plotted in grey
    2. today_high and today_low plotted in green and maroon respectively
    3. ENEX points are plotted on the blue price line in colours of White, Lime, Maroon and Red
  2. The second graph is irrelevant but captures 
    1. Buying/selling levels in cycles of green and red
    2. Squeezes in yellow
  3. Third graph
    1. rsi_lvl in purple
    2. ema_lvl in yellow
  4. Forth graph
    1. tci variable plotted in purple




































So far, the algorithm seems to be providing a reasonable conclusion and has worked for PK, given its historical data from the following timeframes in May 2020;
  • 1st May (worked)
  • 2nd to 3rd May (market closed)
  • 4th to 5th May (worked)
  • 6th may (failed)
  • 7th to 8th May (worked)
  • 9th to 10th (market closed)
  • 11th to 12th (failed)
  • 13th to 15th  (worked)
  • 16th to 17th (market closed)
  • 18th to 21st (worked)
  • 22nd (failed)
  • 23rd to 25th (market closed + long weekend)
  • 26th May (worked)