1

I'm trying to make a program where the user choses several conditions with "and" and "or", so there are many possibilities. The program gets a string input, however I want that string input to become a condition in a if statement

for instance, if the user types (which will be the standard) "RSI > 30 and MACD > 0" (which will be the standard) the new if statement in the program shall be

if row["RSI"] > 30 and row["MACD"] > 0:
   return True

I know that I can use if statements to store each entry of the user but it would be very heavy for the program and use a lot of code (because of the and/or mainly). Note that I'm a beginner in Python

Thanks a lot hope you'll understand

itisku
  • 35
  • 5
  • how many can they have/add (how many options are there)? I'd imagine you'd probably need to develop a parser and then handle everything – depperm Sep 27 '21 at 16:03
  • https://gyazo.com/32fdfbd2758cf6041e70351fcc7b3d14 I don't know how to use a parser but I'll check it out – itisku Sep 27 '21 at 16:07
  • so 1-3 options, and the options are hardcode in (it doesn't look like there is typing in the example video) – depperm Sep 27 '21 at 16:11
  • indeed there is no typing but the type returned by the scrollbar list is string so its kinda the same – itisku Sep 27 '21 at 16:12

1 Answers1

1

Using exec() or eval() is fine if you will tightly control the code that is used as an input to those functions, but it can lead to unsafe code and is a lazy coding practice when there are other safer ways to do what you want. Something like this could work where you parse the input, and use predefined functions to evaluate them:

First, define the functions that you'll use, and add them to a dictionary:

def gt(row, key, value)
    '''Returns True if row[key] > value'''
    return row[key] > value

def lt(row, key, value)
    '''Returns True if row[key] < value'''
    return row[key] < value

def andf(val1, val2):
    '''Returns val1 and val2'''
    return val1 and val2

def orf(val1, val2):
    '''Returns val1 or val2'''
    return val1 or val2

operations = {">": gt, "<": lt, "and": andf, "or": orf}

Then, ask the user for their input, and parse and evaluate them. Functions are first-class citizens in python, which means you can treat them like any other object: in our case, we're going to store the function objects in a tuple that we create when we parse the user input. We will access these function objects through the tuple, and call them using the other elements of the tuple as arguments.

num_conditions = 3

conditions = []
bool_opers = []
for i in range(num_conditions):
    c = input("Enter condition (e.g. RSI > 30): ").split(" ")
    key = c[0]
    operator = c[1]
    value = float(c[2])
    # Get the function object that corresponds to the input operator
    op_func = operations[operator] 
    # Create a tuple containing the function object, key, and value and save it for later
    conditions.append((op_func, key, value))
    # Do the same thing for and/or
    if i < (num_conditions - 1):
        b_op = input("Enter and / or: ")
        bool_opers.append(operations[b_op])

Now, suppose we want to apply the filter RSI > 30 and MACD > 0 and MA20 < 5, we can run the code with the following inputs:

Enter condition (e.g. RSI > 30): RSI > 30
Enter and / or: and
Enter condition (e.g. RSI > 30): MACD > 0
Enter and / or: and
Enter condition (e.g. RSI > 30): MA20 < 5

conditions is now

[(<function __main__.gt(row, key, value)>, 'RSI', 30.0),
 (<function __main__.gt(row, key, value)>, 'MACD', 0.0),
 (<function __main__.lt(row, key, value)>, 'MA20', 5.0)]

Each element of conditions is a tuple, which contains the function object for comparison, the key in the row, and the value for comparison.

and bool_opers is:

[<function __main__.andf(val1, val2)>, <function __main__.andf(val1, val2)>]

Finally, we need to iterate over each row, and call the functions we've saved. Let's say the data looks like so:

data = [{'RSI': 50, 'MACD': 10, 'MA20': 10},
 {'RSI': 20, 'MACD': 10, 'MA20': 2},
 {'RSI': 50, 'MACD': -10, 'MA20': 10},
 {'RSI': 40, 'MACD': 10, 'MA20': 3},
 {'RSI': 35, 'MACD': 20, 'MA20': 0}]
selected_rows = []
for row in data:
    # Calculate all elements of `conditions`
    conditions_resolved = [c[0](row, c[1], c[2]) for c in conditions]
    # e.g the first element will be
    # c[0]: the function object gt
    # c[1]: The string "RSI"
    # c[2]: The number 30.
    # So this is equivalent to doing:
    # gt(row, 'RSI', 30)

    # Set the result as the value of the first condition
    result = conditions_resolved[0] 

    # If you have 2 or more conditions
    if num_conditions >= 2:
        # Take subsequent bool_opers and conditions_resolved
        # And successively apply the operation to the current result and the new condition
        for operation, val2 in zip(bool_opers, conditions_resolved[1:]):
            result = operation(result, val2)
    
    # If final result is True, add row to selected_rows
    if result: 
        selected_rows.append(row)

print(selected_rows)

Which selects the last two rows (the only ones that satisfy all our conditions)

[{'RSI': 40, 'MACD': 10, 'MA20': 3}, {'RSI': 35, 'MACD': 20, 'MA20': 0}]
Pranav Hosangadi
  • 23,755
  • 7
  • 44
  • 70
  • thanks a lot, I am a beginner so I am not really familiar with all this code but I'll look into it deeply – itisku Sep 27 '21 at 16:46
  • @itisku No worries. It's really not that complicated if you break it down and understand it bit-by-bit, let me know if you have any questions. – Pranav Hosangadi Sep 27 '21 at 16:48