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.