Deeper dive into Relational Operators

Determining what is happening with price and indicators is essential for bots – not to mention confirming a valid value for any setting. Here we take a deeper dive into Relational Operators and how to use them for directional information and validation.

Simplifying information

It is easy for a human to determine whether price is moving up or down, but it might not be so straight-forward to do so in programming. Simplifying information by pre-processing it can be beneficial and help further decision-making – not to mention that the code can also be more human-readable.
Let’s take a look at the following example:

-- variables
local close           = ClosePrices()
local current_close   = close[1]
local previous_close  = close[2]

-- define price direction
local price_direction = "none"

-- current price is-bigger-than previous price
if current_close > previous_close then
    price_direction = "up"
    
-- current price is-smaller-than previous price
elseif current_close < previous_close then
    price_direction = "down"
end

-- print the information to log
Log( price_direction )

In the above example, we determine the direction of the close price based on the last 2 values.
This is basically what Relational Operators are used for.

But it doesn’t end there; we can even define candlestick chart’s candle types too. In the example below, we determine 2 different candle types: Gravestone and Dragonfly.

-- grab prices
local o = OpenPrices()
local h = HighPrices()
local l = LowPrices()
local c = ClosePrices()
local ohlc = OHLCPrices()


-- define candle type
local candle_type = "none"

-- gravestone type
if o == c and c == l and h > c then
    candle_type = "gravestone"

-- dragonfly type
elseif o == c and c == h and l < c then
    candle_type = "dragonfly"
end


-- plot text to chart when candle_type is not-equal-to "none"
if candle_type != "none" then
    PlotShape(0, ShapeText, White, 8, true, candle_type, White)
end

The example is very simplistic, but should hint for a bigger picture.

Acting based on settings

Let’s take a look at one more example. Instead of defining the direction of a price or the type of candle, we will be confirming and acting based on some simple user-defined settings.

-- some options
local options = {
    rsi   = 'RSI',
    mfi   = 'MFI',
    cci   = 'CCI',
    thisIsNotHandled = 'I give you error.'
}

-- input settings
local oscillator_type = InputOptions(
    'Oscillator Type', -- visible parameter name
    options.rsi,       -- default value
    options            -- available options
)
local oscillator_period = Input('Oscillator Period Length', 20)


-- price data
local h = HighPrices()
local l = LowPrices()
local c = ClosePrices()
local v = GetVolume()


-- unassigned variable for oscillator values
local oscillator_values


-- confirm and calculate correct values
if oscillator_type == options.rsi then
    oscillator_values = RSI(c, oscillator_period)

elseif oscillator_type == options.mfi then
    oscillator_values = MFI(h, l, c, v, oscillator_period)

elseif oscillator_type == options.cci then
    oscillator_values = CCI(h, l, c, oscillator_period)

-- handle invalid or not-implemented options
else
    LogError('Invalid or not implemented oscillator type: "' .. oscillator_type .. '"')
    return
end

Plot(1, 'Oscillator', oscillator_values)

In the above example we offer three different oscillator types for anyone to select from and calculate the oscillator values correctly based on this input, before plotting the values on the chart. Also, in case of an invalid or “not implemented” value, we throw an error to be noticed and return from script execution.

Mind the flow…

We need to understand that there are cases where >= might block access to testing only for > operator, similarly with <= and < operators. If we stay mindful of the order of tests, we can make better code.
Here are few examples of what is meant:

A = 10
B = 5

if A > B then
    Log( 'A > B' )
elseif A >= B then
    Log( 'A >= B' )
end

As we can see, the code above is completely valid, but the elseif part never gets executed unless both A and B hold the same value. Therefor the logged result will always be A > B unless the values are equal – or nothing is logged when A is smaller than B.

The next example will separate the two if-statements and flip the operators:

A = 5
B = 10

if A < B then
    Log( 'A < B' )
end

if A <= B then
    Log( 'A <= B' )
end

This way, we are now able to determine both results whenever A is less than or equal to B, and only run the other when A is less than B. I know this example might seem silly, but there is truth hidden into this.

So, to be mindful about this problem, we could refactor the logic to look something like this:

A = 10
B = 5

if A == B then
    Log( 'A is equal to B' )
elseif A > B then
    Log( 'A is bigger than B' )
elseif A < B then
    Log( 'A is less than B' )
end

Decimals?

When dealing with price and indicator values that are more accurate than whole-numbers, it is good to remember that it is far-fetched (like way too @&!”¤# far) to believe that trades would trigger when a price or an indicator value equals to something specific. This will of course happen with a system that is designed to round values into whole numbers!

But, try to run a backtest with the following script and see how many times you get a trade to occur at your own specific price:

local my_price_trigger = <insert exact price here>

local c = ClosePrices()

if c == my_price_trigger then
    DoLong()
end

If you are able to get a backtest run that triggers (or after the actual entry, tries to trigger again) a long trade, you should buy me a coffee just to make sure I am awake next time I write these things. But more to the point; you should never do it like this – NEVER! Best practice is to always have some kind of threshold for your triggers, or make it so it is either “less than or equal to” or “greater than or equal to” – if you catch my drift.

Back to: HaasScript Fundamentals > Operators